vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php line 44

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Twig\NodeVisitor;
  11. use Twig\Environment;
  12. use Twig\Extension\EscaperExtension;
  13. use Twig\Node\AutoEscapeNode;
  14. use Twig\Node\BlockNode;
  15. use Twig\Node\BlockReferenceNode;
  16. use Twig\Node\DoNode;
  17. use Twig\Node\Expression\ConditionalExpression;
  18. use Twig\Node\Expression\ConstantExpression;
  19. use Twig\Node\Expression\FilterExpression;
  20. use Twig\Node\Expression\InlinePrint;
  21. use Twig\Node\ImportNode;
  22. use Twig\Node\ModuleNode;
  23. use Twig\Node\Node;
  24. use Twig\Node\PrintNode;
  25. use Twig\NodeTraverser;
  26. /**
  27. * @author Fabien Potencier <fabien@symfony.com>
  28. */
  29. final class EscaperNodeVisitor extends AbstractNodeVisitor
  30. {
  31. private $statusStack = [];
  32. private $blocks = [];
  33. private $safeAnalysis;
  34. private $traverser;
  35. private $defaultStrategy = false;
  36. private $safeVars = [];
  37. public function __construct()
  38. {
  39. $this->safeAnalysis = new SafeAnalysisNodeVisitor();
  40. }
  41. protected function doEnterNode(Node $node, Environment $env)
  42. {
  43. if ($node instanceof ModuleNode) {
  44. if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) {
  45. $this->defaultStrategy = $defaultStrategy;
  46. }
  47. $this->safeVars = [];
  48. $this->blocks = [];
  49. } elseif ($node instanceof AutoEscapeNode) {
  50. $this->statusStack[] = $node->getAttribute('value');
  51. } elseif ($node instanceof BlockNode) {
  52. $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
  53. } elseif ($node instanceof ImportNode) {
  54. $this->safeVars[] = $node->getNode('var')->getAttribute('name');
  55. }
  56. return $node;
  57. }
  58. protected function doLeaveNode(Node $node, Environment $env)
  59. {
  60. if ($node instanceof ModuleNode) {
  61. $this->defaultStrategy = false;
  62. $this->safeVars = [];
  63. $this->blocks = [];
  64. } elseif ($node instanceof FilterExpression) {
  65. return $this->preEscapeFilterNode($node, $env);
  66. } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) {
  67. $expression = $node->getNode('expr');
  68. if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) {
  69. return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine());
  70. }
  71. return $this->escapePrintNode($node, $env, $type);
  72. }
  73. if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) {
  74. array_pop($this->statusStack);
  75. } elseif ($node instanceof BlockReferenceNode) {
  76. $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
  77. }
  78. return $node;
  79. }
  80. private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, $type)
  81. {
  82. $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env);
  83. $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env);
  84. return $expr2Safe !== $expr3Safe;
  85. }
  86. private function unwrapConditional(ConditionalExpression $expression, Environment $env, $type)
  87. {
  88. // convert "echo a ? b : c" to "a ? echo b : echo c" recursively
  89. $expr2 = $expression->getNode('expr2');
  90. if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) {
  91. $expr2 = $this->unwrapConditional($expr2, $env, $type);
  92. } else {
  93. $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type);
  94. }
  95. $expr3 = $expression->getNode('expr3');
  96. if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) {
  97. $expr3 = $this->unwrapConditional($expr3, $env, $type);
  98. } else {
  99. $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type);
  100. }
  101. return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine());
  102. }
  103. private function escapeInlinePrintNode(InlinePrint $node, Environment $env, $type)
  104. {
  105. $expression = $node->getNode('node');
  106. if ($this->isSafeFor($type, $expression, $env)) {
  107. return $node;
  108. }
  109. return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
  110. }
  111. private function escapePrintNode(PrintNode $node, Environment $env, $type)
  112. {
  113. if (false === $type) {
  114. return $node;
  115. }
  116. $expression = $node->getNode('expr');
  117. if ($this->isSafeFor($type, $expression, $env)) {
  118. return $node;
  119. }
  120. $class = \get_class($node);
  121. return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
  122. }
  123. private function preEscapeFilterNode(FilterExpression $filter, Environment $env)
  124. {
  125. $name = $filter->getNode('filter')->getAttribute('value');
  126. $type = $env->getFilter($name)->getPreEscape();
  127. if (null === $type) {
  128. return $filter;
  129. }
  130. $node = $filter->getNode('node');
  131. if ($this->isSafeFor($type, $node, $env)) {
  132. return $filter;
  133. }
  134. $filter->setNode('node', $this->getEscaperFilter($type, $node));
  135. return $filter;
  136. }
  137. private function isSafeFor($type, Node $expression, $env)
  138. {
  139. $safe = $this->safeAnalysis->getSafe($expression);
  140. if (null === $safe) {
  141. if (null === $this->traverser) {
  142. $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]);
  143. }
  144. $this->safeAnalysis->setSafeVars($this->safeVars);
  145. $this->traverser->traverse($expression);
  146. $safe = $this->safeAnalysis->getSafe($expression);
  147. }
  148. return \in_array($type, $safe) || \in_array('all', $safe);
  149. }
  150. private function needEscaping(Environment $env)
  151. {
  152. if (\count($this->statusStack)) {
  153. return $this->statusStack[\count($this->statusStack) - 1];
  154. }
  155. return $this->defaultStrategy ? $this->defaultStrategy : false;
  156. }
  157. private function getEscaperFilter(string $type, Node $node): FilterExpression
  158. {
  159. $line = $node->getTemplateLine();
  160. $name = new ConstantExpression('escape', $line);
  161. $args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]);
  162. return new FilterExpression($node, $name, $args, $line);
  163. }
  164. public function getPriority()
  165. {
  166. return 0;
  167. }
  168. }
  169. class_alias('Twig\NodeVisitor\EscaperNodeVisitor', 'Twig_NodeVisitor_Escaper');