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