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