aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/Twig/NodeVisitor
diff options
context:
space:
mode:
authorNicolas LÅ“uillet <nicolas.loeuillet@gmail.com>2013-08-02 22:40:51 +0200
committerNicolas LÅ“uillet <nicolas.loeuillet@gmail.com>2013-08-02 22:40:51 +0200
commita4565e88edbc8e3bd092a475469769c86a4c350c (patch)
treea6a3c935b03a23ff87575c8c315cf8ba78fe68c2 /inc/Twig/NodeVisitor
parentf6c9baab3efeec1d0efa151e276fc08d5b58f9e9 (diff)
downloadwallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.tar.gz
wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.tar.zst
wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.zip
add Twig & refactor poche
Diffstat (limited to 'inc/Twig/NodeVisitor')
-rw-r--r--inc/Twig/NodeVisitor/Escaper.php167
-rw-r--r--inc/Twig/NodeVisitor/Optimizer.php246
-rw-r--r--inc/Twig/NodeVisitor/SafeAnalysis.php131
-rw-r--r--inc/Twig/NodeVisitor/Sandbox.php92
4 files changed, 636 insertions, 0 deletions
diff --git a/inc/Twig/NodeVisitor/Escaper.php b/inc/Twig/NodeVisitor/Escaper.php
new file mode 100644
index 00000000..cc4b3d71
--- /dev/null
+++ b/inc/Twig/NodeVisitor/Escaper.php
@@ -0,0 +1,167 @@
1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) 2009 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
12/**
13 * Twig_NodeVisitor_Escaper implements output escaping.
14 *
15 * @author Fabien Potencier <fabien@symfony.com>
16 */
17class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
18{
19 protected $statusStack = array();
20 protected $blocks = array();
21 protected $safeAnalysis;
22 protected $traverser;
23 protected $defaultStrategy = false;
24 protected $safeVars = array();
25
26 public function __construct()
27 {
28 $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis();
29 }
30
31 /**
32 * Called before child nodes are visited.
33 *
34 * @param Twig_NodeInterface $node The node to visit
35 * @param Twig_Environment $env The Twig environment instance
36 *
37 * @return Twig_NodeInterface The modified node
38 */
39 public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
40 {
41 if ($node instanceof Twig_Node_Module) {
42 if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) {
43 $this->defaultStrategy = $defaultStrategy;
44 }
45 $this->safeVars = array();
46 } elseif ($node instanceof Twig_Node_AutoEscape) {
47 $this->statusStack[] = $node->getAttribute('value');
48 } elseif ($node instanceof Twig_Node_Block) {
49 $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
50 } elseif ($node instanceof Twig_Node_Import) {
51 $this->safeVars[] = $node->getNode('var')->getAttribute('name');
52 }
53
54 return $node;
55 }
56
57 /**
58 * Called after child nodes are visited.
59 *
60 * @param Twig_NodeInterface $node The node to visit
61 * @param Twig_Environment $env The Twig environment instance
62 *
63 * @return Twig_NodeInterface The modified node
64 */
65 public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
66 {
67 if ($node instanceof Twig_Node_Module) {
68 $this->defaultStrategy = false;
69 $this->safeVars = array();
70 } elseif ($node instanceof Twig_Node_Expression_Filter) {
71 return $this->preEscapeFilterNode($node, $env);
72 } elseif ($node instanceof Twig_Node_Print) {
73 return $this->escapePrintNode($node, $env, $this->needEscaping($env));
74 }
75
76 if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
77 array_pop($this->statusStack);
78 } elseif ($node instanceof Twig_Node_BlockReference) {
79 $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
80 }
81
82 return $node;
83 }
84
85 protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type)
86 {
87 if (false === $type) {
88 return $node;
89 }
90
91 $expression = $node->getNode('expr');
92
93 if ($this->isSafeFor($type, $expression, $env)) {
94 return $node;
95 }
96
97 $class = get_class($node);
98
99 return new $class(
100 $this->getEscaperFilter($type, $expression),
101 $node->getLine()
102 );
103 }
104
105 protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env)
106 {
107 $name = $filter->getNode('filter')->getAttribute('value');
108
109 $type = $env->getFilter($name)->getPreEscape();
110 if (null === $type) {
111 return $filter;
112 }
113
114 $node = $filter->getNode('node');
115 if ($this->isSafeFor($type, $node, $env)) {
116 return $filter;
117 }
118
119 $filter->setNode('node', $this->getEscaperFilter($type, $node));
120
121 return $filter;
122 }
123
124 protected function isSafeFor($type, Twig_NodeInterface $expression, $env)
125 {
126 $safe = $this->safeAnalysis->getSafe($expression);
127
128 if (null === $safe) {
129 if (null === $this->traverser) {
130 $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis));
131 }
132
133 $this->safeAnalysis->setSafeVars($this->safeVars);
134
135 $this->traverser->traverse($expression);
136 $safe = $this->safeAnalysis->getSafe($expression);
137 }
138
139 return in_array($type, $safe) || in_array('all', $safe);
140 }
141
142 protected function needEscaping(Twig_Environment $env)
143 {
144 if (count($this->statusStack)) {
145 return $this->statusStack[count($this->statusStack) - 1];
146 }
147
148 return $this->defaultStrategy ? $this->defaultStrategy : false;
149 }
150
151 protected function getEscaperFilter($type, Twig_NodeInterface $node)
152 {
153 $line = $node->getLine();
154 $name = new Twig_Node_Expression_Constant('escape', $line);
155 $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line), new Twig_Node_Expression_Constant(null, $line), new Twig_Node_Expression_Constant(true, $line)));
156
157 return new Twig_Node_Expression_Filter($node, $name, $args, $line);
158 }
159
160 /**
161 * {@inheritdoc}
162 */
163 public function getPriority()
164 {
165 return 0;
166 }
167}
diff --git a/inc/Twig/NodeVisitor/Optimizer.php b/inc/Twig/NodeVisitor/Optimizer.php
new file mode 100644
index 00000000..a254def7
--- /dev/null
+++ b/inc/Twig/NodeVisitor/Optimizer.php
@@ -0,0 +1,246 @@
1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) 2010 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
12/**
13 * Twig_NodeVisitor_Optimizer tries to optimizes the AST.
14 *
15 * This visitor is always the last registered one.
16 *
17 * You can configure which optimizations you want to activate via the
18 * optimizer mode.
19 *
20 * @author Fabien Potencier <fabien@symfony.com>
21 */
22class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
23{
24 const OPTIMIZE_ALL = -1;
25 const OPTIMIZE_NONE = 0;
26 const OPTIMIZE_FOR = 2;
27 const OPTIMIZE_RAW_FILTER = 4;
28 const OPTIMIZE_VAR_ACCESS = 8;
29
30 protected $loops = array();
31 protected $optimizers;
32 protected $prependedNodes = array();
33 protected $inABody = false;
34
35 /**
36 * Constructor.
37 *
38 * @param integer $optimizers The optimizer mode
39 */
40 public function __construct($optimizers = -1)
41 {
42 if (!is_int($optimizers) || $optimizers > 2) {
43 throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
44 }
45
46 $this->optimizers = $optimizers;
47 }
48
49 /**
50 * {@inheritdoc}
51 */
52 public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
53 {
54 if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
55 $this->enterOptimizeFor($node, $env);
56 }
57
58 if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
59 if ($this->inABody) {
60 if (!$node instanceof Twig_Node_Expression) {
61 if (get_class($node) !== 'Twig_Node') {
62 array_unshift($this->prependedNodes, array());
63 }
64 } else {
65 $node = $this->optimizeVariables($node, $env);
66 }
67 } elseif ($node instanceof Twig_Node_Body) {
68 $this->inABody = true;
69 }
70 }
71
72 return $node;
73 }
74
75 /**
76 * {@inheritdoc}
77 */
78 public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
79 {
80 $expression = $node instanceof Twig_Node_Expression;
81
82 if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
83 $this->leaveOptimizeFor($node, $env);
84 }
85
86 if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
87 $node = $this->optimizeRawFilter($node, $env);
88 }
89
90 $node = $this->optimizePrintNode($node, $env);
91
92 if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
93 if ($node instanceof Twig_Node_Body) {
94 $this->inABody = false;
95 } elseif ($this->inABody) {
96 if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) {
97 $nodes = array();
98 foreach (array_unique($prependedNodes) as $name) {
99 $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
100 }
101
102 $nodes[] = $node;
103 $node = new Twig_Node($nodes);
104 }
105 }
106 }
107
108 return $node;
109 }
110
111 protected function optimizeVariables($node, $env)
112 {
113 if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
114 $this->prependedNodes[0][] = $node->getAttribute('name');
115
116 return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
117 }
118
119 return $node;
120 }
121
122 /**
123 * Optimizes print nodes.
124 *
125 * It replaces:
126 *
127 * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
128 *
129 * @param Twig_NodeInterface $node A Node
130 * @param Twig_Environment $env The current Twig environment
131 */
132 protected function optimizePrintNode($node, $env)
133 {
134 if (!$node instanceof Twig_Node_Print) {
135 return $node;
136 }
137
138 if (
139 $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
140 $node->getNode('expr') instanceof Twig_Node_Expression_Parent
141 ) {
142 $node->getNode('expr')->setAttribute('output', true);
143
144 return $node->getNode('expr');
145 }
146
147 return $node;
148 }
149
150 /**
151 * Removes "raw" filters.
152 *
153 * @param Twig_NodeInterface $node A Node
154 * @param Twig_Environment $env The current Twig environment
155 */
156 protected function optimizeRawFilter($node, $env)
157 {
158 if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
159 return $node->getNode('node');
160 }
161
162 return $node;
163 }
164
165 /**
166 * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
167 *
168 * @param Twig_NodeInterface $node A Node
169 * @param Twig_Environment $env The current Twig environment
170 */
171 protected function enterOptimizeFor($node, $env)
172 {
173 if ($node instanceof Twig_Node_For) {
174 // disable the loop variable by default
175 $node->setAttribute('with_loop', false);
176 array_unshift($this->loops, $node);
177 } elseif (!$this->loops) {
178 // we are outside a loop
179 return;
180 }
181
182 // when do we need to add the loop variable back?
183
184 // the loop variable is referenced for the current loop
185 elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
186 $this->addLoopToCurrent();
187 }
188
189 // block reference
190 elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
191 $this->addLoopToCurrent();
192 }
193
194 // include without the only attribute
195 elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
196 $this->addLoopToAll();
197 }
198
199 // the loop variable is referenced via an attribute
200 elseif ($node instanceof Twig_Node_Expression_GetAttr
201 && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
202 || 'parent' === $node->getNode('attribute')->getAttribute('value')
203 )
204 && (true === $this->loops[0]->getAttribute('with_loop')
205 || ($node->getNode('node') instanceof Twig_Node_Expression_Name
206 && 'loop' === $node->getNode('node')->getAttribute('name')
207 )
208 )
209 ) {
210 $this->addLoopToAll();
211 }
212 }
213
214 /**
215 * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
216 *
217 * @param Twig_NodeInterface $node A Node
218 * @param Twig_Environment $env The current Twig environment
219 */
220 protected function leaveOptimizeFor($node, $env)
221 {
222 if ($node instanceof Twig_Node_For) {
223 array_shift($this->loops);
224 }
225 }
226
227 protected function addLoopToCurrent()
228 {
229 $this->loops[0]->setAttribute('with_loop', true);
230 }
231
232 protected function addLoopToAll()
233 {
234 foreach ($this->loops as $loop) {
235 $loop->setAttribute('with_loop', true);
236 }
237 }
238
239 /**
240 * {@inheritdoc}
241 */
242 public function getPriority()
243 {
244 return 255;
245 }
246}
diff --git a/inc/Twig/NodeVisitor/SafeAnalysis.php b/inc/Twig/NodeVisitor/SafeAnalysis.php
new file mode 100644
index 00000000..c4bbd812
--- /dev/null
+++ b/inc/Twig/NodeVisitor/SafeAnalysis.php
@@ -0,0 +1,131 @@
1<?php
2
3class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
4{
5 protected $data = array();
6 protected $safeVars = array();
7
8 public function setSafeVars($safeVars)
9 {
10 $this->safeVars = $safeVars;
11 }
12
13 public function getSafe(Twig_NodeInterface $node)
14 {
15 $hash = spl_object_hash($node);
16 if (isset($this->data[$hash])) {
17 foreach ($this->data[$hash] as $bucket) {
18 if ($bucket['key'] === $node) {
19 return $bucket['value'];
20 }
21 }
22 }
23 }
24
25 protected function setSafe(Twig_NodeInterface $node, array $safe)
26 {
27 $hash = spl_object_hash($node);
28 if (isset($this->data[$hash])) {
29 foreach ($this->data[$hash] as &$bucket) {
30 if ($bucket['key'] === $node) {
31 $bucket['value'] = $safe;
32
33 return;
34 }
35 }
36 }
37 $this->data[$hash][] = array(
38 'key' => $node,
39 'value' => $safe,
40 );
41 }
42
43 public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
44 {
45 return $node;
46 }
47
48 public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
49 {
50 if ($node instanceof Twig_Node_Expression_Constant) {
51 // constants are marked safe for all
52 $this->setSafe($node, array('all'));
53 } elseif ($node instanceof Twig_Node_Expression_BlockReference) {
54 // blocks are safe by definition
55 $this->setSafe($node, array('all'));
56 } elseif ($node instanceof Twig_Node_Expression_Parent) {
57 // parent block is safe by definition
58 $this->setSafe($node, array('all'));
59 } elseif ($node instanceof Twig_Node_Expression_Conditional) {
60 // intersect safeness of both operands
61 $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3')));
62 $this->setSafe($node, $safe);
63 } elseif ($node instanceof Twig_Node_Expression_Filter) {
64 // filter expression is safe when the filter is safe
65 $name = $node->getNode('filter')->getAttribute('value');
66 $args = $node->getNode('arguments');
67 if (false !== $filter = $env->getFilter($name)) {
68 $safe = $filter->getSafe($args);
69 if (null === $safe) {
70 $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety());
71 }
72 $this->setSafe($node, $safe);
73 } else {
74 $this->setSafe($node, array());
75 }
76 } elseif ($node instanceof Twig_Node_Expression_Function) {
77 // function expression is safe when the function is safe
78 $name = $node->getAttribute('name');
79 $args = $node->getNode('arguments');
80 $function = $env->getFunction($name);
81 if (false !== $function) {
82 $this->setSafe($node, $function->getSafe($args));
83 } else {
84 $this->setSafe($node, array());
85 }
86 } elseif ($node instanceof Twig_Node_Expression_MethodCall) {
87 if ($node->getAttribute('safe')) {
88 $this->setSafe($node, array('all'));
89 } else {
90 $this->setSafe($node, array());
91 }
92 } elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) {
93 $name = $node->getNode('node')->getAttribute('name');
94 // attributes on template instances are safe
95 if ('_self' == $name || in_array($name, $this->safeVars)) {
96 $this->setSafe($node, array('all'));
97 } else {
98 $this->setSafe($node, array());
99 }
100 } else {
101 $this->setSafe($node, array());
102 }
103
104 return $node;
105 }
106
107 protected function intersectSafe(array $a = null, array $b = null)
108 {
109 if (null === $a || null === $b) {
110 return array();
111 }
112
113 if (in_array('all', $a)) {
114 return $b;
115 }
116
117 if (in_array('all', $b)) {
118 return $a;
119 }
120
121 return array_intersect($a, $b);
122 }
123
124 /**
125 * {@inheritdoc}
126 */
127 public function getPriority()
128 {
129 return 0;
130 }
131}
diff --git a/inc/Twig/NodeVisitor/Sandbox.php b/inc/Twig/NodeVisitor/Sandbox.php
new file mode 100644
index 00000000..fb27045b
--- /dev/null
+++ b/inc/Twig/NodeVisitor/Sandbox.php
@@ -0,0 +1,92 @@
1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) 2009 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
12/**
13 * Twig_NodeVisitor_Sandbox implements sandboxing.
14 *
15 * @author Fabien Potencier <fabien@symfony.com>
16 */
17class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
18{
19 protected $inAModule = false;
20 protected $tags;
21 protected $filters;
22 protected $functions;
23
24 /**
25 * Called before child nodes are visited.
26 *
27 * @param Twig_NodeInterface $node The node to visit
28 * @param Twig_Environment $env The Twig environment instance
29 *
30 * @return Twig_NodeInterface The modified node
31 */
32 public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
33 {
34 if ($node instanceof Twig_Node_Module) {
35 $this->inAModule = true;
36 $this->tags = array();
37 $this->filters = array();
38 $this->functions = array();
39
40 return $node;
41 } elseif ($this->inAModule) {
42 // look for tags
43 if ($node->getNodeTag()) {
44 $this->tags[] = $node->getNodeTag();
45 }
46
47 // look for filters
48 if ($node instanceof Twig_Node_Expression_Filter) {
49 $this->filters[] = $node->getNode('filter')->getAttribute('value');
50 }
51
52 // look for functions
53 if ($node instanceof Twig_Node_Expression_Function) {
54 $this->functions[] = $node->getAttribute('name');
55 }
56
57 // wrap print to check __toString() calls
58 if ($node instanceof Twig_Node_Print) {
59 return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag());
60 }
61 }
62
63 return $node;
64 }
65
66 /**
67 * Called after child nodes are visited.
68 *
69 * @param Twig_NodeInterface $node The node to visit
70 * @param Twig_Environment $env The Twig environment instance
71 *
72 * @return Twig_NodeInterface The modified node
73 */
74 public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
75 {
76 if ($node instanceof Twig_Node_Module) {
77 $this->inAModule = false;
78
79 return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions));
80 }
81
82 return $node;
83 }
84
85 /**
86 * {@inheritdoc}
87 */
88 public function getPriority()
89 {
90 return 0;
91 }
92}