blob: 90d6f2e0fd03edb31143858f1513100ffea61d2c [file] [log] [blame]
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +01001<?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 * @internal
28 */
29final class SafeAnalysisNodeVisitor implements NodeVisitorInterface
30{
31 private $data = [];
32 private $safeVars = [];
33
34 public function setSafeVars(array $safeVars): void
35 {
36 $this->safeVars = $safeVars;
37 }
38
39 public function getSafe(Node $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 private function setSafe(Node $node, array $safe): void
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 public function enterNode(Node $node, Environment $env): Node
78 {
79 return $node;
80 }
81
82 public function leaveNode(Node $node, Environment $env): ?Node
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 ($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 if ($function = $env->getFunction($name)) {
115 $this->setSafe($node, $function->getSafe($args));
116 } else {
117 $this->setSafe($node, []);
118 }
119 } elseif ($node instanceof MethodCallExpression) {
120 if ($node->getAttribute('safe')) {
121 $this->setSafe($node, ['all']);
122 } else {
123 $this->setSafe($node, []);
124 }
125 } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) {
126 $name = $node->getNode('node')->getAttribute('name');
127 if (\in_array($name, $this->safeVars)) {
128 $this->setSafe($node, ['all']);
129 } else {
130 $this->setSafe($node, []);
131 }
132 } else {
133 $this->setSafe($node, []);
134 }
135
136 return $node;
137 }
138
139 private function intersectSafe(array $a = null, array $b = null): array
140 {
141 if (null === $a || null === $b) {
142 return [];
143 }
144
145 if (\in_array('all', $a)) {
146 return $b;
147 }
148
149 if (\in_array('all', $b)) {
150 return $a;
151 }
152
153 return array_intersect($a, $b);
154 }
155
156 public function getPriority(): int
157 {
158 return 0;
159 }
160}