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\Expression\BlockReferenceExpression; 16use Twig\Node\Expression\ConditionalExpression; 17use Twig\Node\Expression\ConstantExpression; 18use Twig\Node\Expression\FilterExpression; 19use Twig\Node\Expression\FunctionExpression; 20use Twig\Node\Expression\GetAttrExpression; 21use Twig\Node\Expression\MethodCallExpression; 22use Twig\Node\Expression\NameExpression; 23use Twig\Node\Expression\ParentExpression; 24use Twig\Node\Node; 25 26/** 27 * @final 28 */ 29class SafeAnalysisNodeVisitor extends AbstractNodeVisitor 30{ 31 protected $data = []; 32 protected $safeVars = []; 33 34 public function setSafeVars($safeVars) 35 { 36 $this->safeVars = $safeVars; 37 } 38 39 public function getSafe(\Twig_NodeInterface $node) 40 { 41 $hash = spl_object_hash($node); 42 if (!isset($this->data[$hash])) { 43 return; 44 } 45 46 foreach ($this->data[$hash] as $bucket) { 47 if ($bucket['key'] !== $node) { 48 continue; 49 } 50 51 if (\in_array('html_attr', $bucket['value'])) { 52 $bucket['value'][] = 'html'; 53 } 54 55 return $bucket['value']; 56 } 57 } 58 59 protected function setSafe(\Twig_NodeInterface $node, array $safe) 60 { 61 $hash = spl_object_hash($node); 62 if (isset($this->data[$hash])) { 63 foreach ($this->data[$hash] as &$bucket) { 64 if ($bucket['key'] === $node) { 65 $bucket['value'] = $safe; 66 67 return; 68 } 69 } 70 } 71 $this->data[$hash][] = [ 72 'key' => $node, 73 'value' => $safe, 74 ]; 75 } 76 77 protected function doEnterNode(Node $node, Environment $env) 78 { 79 return $node; 80 } 81 82 protected function doLeaveNode(Node $node, Environment $env) 83 { 84 if ($node instanceof ConstantExpression) { 85 // constants are marked safe for all 86 $this->setSafe($node, ['all']); 87 } elseif ($node instanceof BlockReferenceExpression) { 88 // blocks are safe by definition 89 $this->setSafe($node, ['all']); 90 } elseif ($node instanceof ParentExpression) { 91 // parent block is safe by definition 92 $this->setSafe($node, ['all']); 93 } elseif ($node instanceof ConditionalExpression) { 94 // intersect safeness of both operands 95 $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); 96 $this->setSafe($node, $safe); 97 } elseif ($node instanceof FilterExpression) { 98 // filter expression is safe when the filter is safe 99 $name = $node->getNode('filter')->getAttribute('value'); 100 $args = $node->getNode('arguments'); 101 if (false !== $filter = $env->getFilter($name)) { 102 $safe = $filter->getSafe($args); 103 if (null === $safe) { 104 $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); 105 } 106 $this->setSafe($node, $safe); 107 } else { 108 $this->setSafe($node, []); 109 } 110 } elseif ($node instanceof FunctionExpression) { 111 // function expression is safe when the function is safe 112 $name = $node->getAttribute('name'); 113 $args = $node->getNode('arguments'); 114 $function = $env->getFunction($name); 115 if (false !== $function) { 116 $this->setSafe($node, $function->getSafe($args)); 117 } else { 118 $this->setSafe($node, []); 119 } 120 } elseif ($node instanceof MethodCallExpression) { 121 if ($node->getAttribute('safe')) { 122 $this->setSafe($node, ['all']); 123 } else { 124 $this->setSafe($node, []); 125 } 126 } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { 127 $name = $node->getNode('node')->getAttribute('name'); 128 // attributes on template instances are safe 129 if ('_self' == $name || \in_array($name, $this->safeVars)) { 130 $this->setSafe($node, ['all']); 131 } else { 132 $this->setSafe($node, []); 133 } 134 } else { 135 $this->setSafe($node, []); 136 } 137 138 return $node; 139 } 140 141 protected function intersectSafe(array $a = null, array $b = null) 142 { 143 if (null === $a || null === $b) { 144 return []; 145 } 146 147 if (\in_array('all', $a)) { 148 return $b; 149 } 150 151 if (\in_array('all', $b)) { 152 return $a; 153 } 154 155 return array_intersect($a, $b); 156 } 157 158 public function getPriority() 159 { 160 return 0; 161 } 162} 163 164class_alias('Twig\NodeVisitor\SafeAnalysisNodeVisitor', 'Twig_NodeVisitor_SafeAnalysis'); 165