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