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