blob: 1446cee6b98146c791e346882b5ec7dc79429b44 [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\CheckSecurityCallNode;
16use Twig\Node\CheckSecurityNode;
17use Twig\Node\CheckToStringNode;
18use Twig\Node\Expression\Binary\ConcatBinary;
19use Twig\Node\Expression\Binary\RangeBinary;
20use Twig\Node\Expression\FilterExpression;
21use Twig\Node\Expression\FunctionExpression;
22use Twig\Node\Expression\GetAttrExpression;
23use Twig\Node\Expression\NameExpression;
24use Twig\Node\ModuleNode;
25use Twig\Node\Node;
26use Twig\Node\PrintNode;
27use Twig\Node\SetNode;
28
29/**
30 * @author Fabien Potencier <fabien@symfony.com>
31 *
32 * @internal
33 */
34final class SandboxNodeVisitor implements NodeVisitorInterface
35{
36 private $inAModule = false;
37 private $tags;
38 private $filters;
39 private $functions;
40 private $needsToStringWrap = false;
41
42 public function enterNode(Node $node, Environment $env): Node
43 {
44 if ($node instanceof ModuleNode) {
45 $this->inAModule = true;
46 $this->tags = [];
47 $this->filters = [];
48 $this->functions = [];
49
50 return $node;
51 } elseif ($this->inAModule) {
52 // look for tags
53 if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) {
54 $this->tags[$node->getNodeTag()] = $node;
55 }
56
57 // look for filters
58 if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) {
59 $this->filters[$node->getNode('filter')->getAttribute('value')] = $node;
60 }
61
62 // look for functions
63 if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) {
64 $this->functions[$node->getAttribute('name')] = $node;
65 }
66
67 // the .. operator is equivalent to the range() function
68 if ($node instanceof RangeBinary && !isset($this->functions['range'])) {
69 $this->functions['range'] = $node;
70 }
71
72 if ($node instanceof PrintNode) {
73 $this->needsToStringWrap = true;
74 $this->wrapNode($node, 'expr');
75 }
76
77 if ($node instanceof SetNode && !$node->getAttribute('capture')) {
78 $this->needsToStringWrap = true;
79 }
80
81 // wrap outer nodes that can implicitly call __toString()
82 if ($this->needsToStringWrap) {
83 if ($node instanceof ConcatBinary) {
84 $this->wrapNode($node, 'left');
85 $this->wrapNode($node, 'right');
86 }
87 if ($node instanceof FilterExpression) {
88 $this->wrapNode($node, 'node');
89 $this->wrapArrayNode($node, 'arguments');
90 }
91 if ($node instanceof FunctionExpression) {
92 $this->wrapArrayNode($node, 'arguments');
93 }
94 }
95 }
96
97 return $node;
98 }
99
100 public function leaveNode(Node $node, Environment $env): ?Node
101 {
102 if ($node instanceof ModuleNode) {
103 $this->inAModule = false;
104
105 $node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));
106 $node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')]));
107 } elseif ($this->inAModule) {
108 if ($node instanceof PrintNode || $node instanceof SetNode) {
109 $this->needsToStringWrap = false;
110 }
111 }
112
113 return $node;
114 }
115
116 private function wrapNode(Node $node, string $name): void
117 {
118 $expr = $node->getNode($name);
119 if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) {
120 $node->setNode($name, new CheckToStringNode($expr));
121 }
122 }
123
124 private function wrapArrayNode(Node $node, string $name): void
125 {
126 $args = $node->getNode($name);
127 foreach ($args as $name => $_) {
128 $this->wrapNode($args, $name);
129 }
130 }
131
132 public function getPriority(): int
133 {
134 return 0;
135 }
136}