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\Node\AutoEscapeNode; 16use Twig\Node\BlockNode; 17use Twig\Node\BlockReferenceNode; 18use Twig\Node\Expression\ConstantExpression; 19use Twig\Node\Expression\FilterExpression; 20use Twig\Node\ImportNode; 21use Twig\Node\ModuleNode; 22use Twig\Node\Node; 23use Twig\Node\PrintNode; 24use Twig\NodeTraverser; 25 26/** 27 * @final 28 * 29 * @author Fabien Potencier <fabien@symfony.com> 30 */ 31class EscaperNodeVisitor extends AbstractNodeVisitor 32{ 33 protected $statusStack = []; 34 protected $blocks = []; 35 protected $safeAnalysis; 36 protected $traverser; 37 protected $defaultStrategy = false; 38 protected $safeVars = []; 39 40 public function __construct() 41 { 42 $this->safeAnalysis = new SafeAnalysisNodeVisitor(); 43 } 44 45 protected function doEnterNode(Node $node, Environment $env) 46 { 47 if ($node instanceof ModuleNode) { 48 if ($env->hasExtension('\Twig\Extension\EscaperExtension') && $defaultStrategy = $env->getExtension('\Twig\Extension\EscaperExtension')->getDefaultStrategy($node->getTemplateName())) { 49 $this->defaultStrategy = $defaultStrategy; 50 } 51 $this->safeVars = []; 52 $this->blocks = []; 53 } elseif ($node instanceof AutoEscapeNode) { 54 $this->statusStack[] = $node->getAttribute('value'); 55 } elseif ($node instanceof BlockNode) { 56 $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); 57 } elseif ($node instanceof ImportNode) { 58 $this->safeVars[] = $node->getNode('var')->getAttribute('name'); 59 } 60 61 return $node; 62 } 63 64 protected function doLeaveNode(Node $node, Environment $env) 65 { 66 if ($node instanceof ModuleNode) { 67 $this->defaultStrategy = false; 68 $this->safeVars = []; 69 $this->blocks = []; 70 } elseif ($node instanceof FilterExpression) { 71 return $this->preEscapeFilterNode($node, $env); 72 } elseif ($node instanceof PrintNode) { 73 return $this->escapePrintNode($node, $env, $this->needEscaping($env)); 74 } 75 76 if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { 77 array_pop($this->statusStack); 78 } elseif ($node instanceof BlockReferenceNode) { 79 $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); 80 } 81 82 return $node; 83 } 84 85 protected function escapePrintNode(PrintNode $node, Environment $env, $type) 86 { 87 if (false === $type) { 88 return $node; 89 } 90 91 $expression = $node->getNode('expr'); 92 93 if ($this->isSafeFor($type, $expression, $env)) { 94 return $node; 95 } 96 97 $class = \get_class($node); 98 99 return new $class( 100 $this->getEscaperFilter($type, $expression), 101 $node->getTemplateLine() 102 ); 103 } 104 105 protected function preEscapeFilterNode(FilterExpression $filter, Environment $env) 106 { 107 $name = $filter->getNode('filter')->getAttribute('value'); 108 109 $type = $env->getFilter($name)->getPreEscape(); 110 if (null === $type) { 111 return $filter; 112 } 113 114 $node = $filter->getNode('node'); 115 if ($this->isSafeFor($type, $node, $env)) { 116 return $filter; 117 } 118 119 $filter->setNode('node', $this->getEscaperFilter($type, $node)); 120 121 return $filter; 122 } 123 124 protected function isSafeFor($type, \Twig_NodeInterface $expression, $env) 125 { 126 $safe = $this->safeAnalysis->getSafe($expression); 127 128 if (null === $safe) { 129 if (null === $this->traverser) { 130 $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]); 131 } 132 133 $this->safeAnalysis->setSafeVars($this->safeVars); 134 135 $this->traverser->traverse($expression); 136 $safe = $this->safeAnalysis->getSafe($expression); 137 } 138 139 return \in_array($type, $safe) || \in_array('all', $safe); 140 } 141 142 protected function needEscaping(Environment $env) 143 { 144 if (\count($this->statusStack)) { 145 return $this->statusStack[\count($this->statusStack) - 1]; 146 } 147 148 return $this->defaultStrategy ? $this->defaultStrategy : false; 149 } 150 151 protected function getEscaperFilter($type, \Twig_NodeInterface $node) 152 { 153 $line = $node->getTemplateLine(); 154 $name = new ConstantExpression('escape', $line); 155 $args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); 156 157 return new FilterExpression($node, $name, $args, $line); 158 } 159 160 public function getPriority() 161 { 162 return 0; 163 } 164} 165 166class_alias('Twig\NodeVisitor\EscaperNodeVisitor', 'Twig_NodeVisitor_Escaper'); 167