This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the Patron package. |
||
5 | * |
||
6 | * (c) Olivier Laviale <[email protected]> |
||
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 | namespace Patron; |
||
13 | |||
14 | use BlueTihi\Context; |
||
15 | |||
16 | /** |
||
17 | * Evaluate expression relative to a context. |
||
18 | */ |
||
19 | class Evaluator |
||
20 | { |
||
21 | const TOKEN_TYPE = 1; |
||
22 | const TOKEN_TYPE_FUNCTION = 2; |
||
23 | const TOKEN_TYPE_IDENTIFIER = 3; |
||
24 | const TOKEN_VALUE = 4; |
||
25 | const TOKEN_ARGS = 5; |
||
26 | const TOKEN_ARGS_EVALUATE = 6; |
||
27 | |||
28 | /** |
||
29 | * @var Engine |
||
30 | */ |
||
31 | private $engine; |
||
32 | |||
33 | /** |
||
34 | * @param Engine $engine |
||
35 | */ |
||
36 | public function __construct(Engine $engine) |
||
37 | { |
||
38 | $this->engine = $engine; |
||
39 | } |
||
40 | |||
41 | /** |
||
42 | * Evaluate an expression relative to a context. |
||
43 | * |
||
44 | * @param mixed $context |
||
45 | * @param string $expression |
||
46 | * @param bool $silent `true` to suppress errors, `false` otherwise. |
||
47 | * |
||
48 | * @return mixed |
||
49 | */ |
||
50 | public function __invoke($context, $expression, $silent = false) |
||
51 | { |
||
52 | $tokens = $this->tokenize($expression); |
||
53 | |||
54 | return $this->evaluate($context, $expression, $tokens, $silent); |
||
55 | } |
||
56 | |||
57 | /** |
||
58 | * Tokenize Javascript style function chain into an array of identifiers and functions |
||
59 | * |
||
60 | * @param string $str |
||
61 | * |
||
62 | * @return array |
||
63 | */ |
||
64 | protected function tokenize($str) |
||
65 | { |
||
66 | if ($str{0} == '@') |
||
67 | { |
||
68 | $str = 'this.' . substr($str, 1); |
||
69 | } |
||
70 | |||
71 | $str .= '.'; |
||
72 | |||
73 | $length = strlen($str); |
||
74 | |||
75 | $quote = null; |
||
76 | $quote_closed = null; |
||
77 | $part = null; |
||
78 | $escape = false; |
||
79 | |||
80 | $function = null; |
||
81 | $args = []; |
||
82 | $args_evaluate = []; |
||
83 | $args_count = 0; |
||
84 | |||
85 | $parts = []; |
||
86 | |||
87 | for ($i = 0 ; $i < $length ; $i++) |
||
88 | { |
||
89 | $c = $str{$i}; |
||
90 | |||
91 | if ($escape) |
||
92 | { |
||
93 | $part .= $c; |
||
94 | |||
95 | $escape = false; |
||
96 | |||
97 | continue; |
||
98 | } |
||
99 | |||
100 | if ($c == '\\') |
||
101 | { |
||
102 | $escape = true; |
||
103 | |||
104 | continue; |
||
105 | } |
||
106 | |||
107 | if ($c == '"' || $c == '\'' || $c == '`') |
||
108 | { |
||
109 | if ($quote && $quote == $c) |
||
0 ignored issues
–
show
|
|||
110 | { |
||
111 | $quote = null; |
||
112 | $quote_closed = $c; |
||
113 | |||
114 | if ($function) |
||
0 ignored issues
–
show
The expression
$function of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
115 | { |
||
116 | continue; |
||
117 | } |
||
118 | } |
||
119 | else if (!$quote) |
||
0 ignored issues
–
show
The expression
$quote of type null|string is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
120 | { |
||
121 | $quote = $c; |
||
122 | |||
123 | if ($function) |
||
0 ignored issues
–
show
The expression
$function of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
124 | { |
||
125 | continue; |
||
126 | } |
||
127 | } |
||
128 | } |
||
129 | |||
130 | if ($quote) |
||
0 ignored issues
–
show
The expression
$quote of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
131 | { |
||
132 | $part .= $c; |
||
133 | |||
134 | continue; |
||
135 | } |
||
136 | |||
137 | # |
||
138 | # we are not in a quote |
||
139 | # |
||
140 | |||
141 | if ($c == '.') |
||
142 | { |
||
143 | if (strlen($part)) |
||
144 | { |
||
145 | $parts[] = [ |
||
146 | |||
147 | self::TOKEN_TYPE => self::TOKEN_TYPE_IDENTIFIER, |
||
148 | self::TOKEN_VALUE => $part |
||
149 | |||
150 | ]; |
||
151 | } |
||
152 | |||
153 | $part = null; |
||
154 | |||
155 | continue; |
||
156 | } |
||
157 | |||
158 | if ($c == '(') |
||
159 | { |
||
160 | $function = $part; |
||
161 | |||
162 | $args = []; |
||
163 | $args_count = 0; |
||
164 | |||
165 | $part = null; |
||
166 | |||
167 | continue; |
||
168 | } |
||
169 | |||
170 | if (($c == ',' || $c == ')') && $function) |
||
0 ignored issues
–
show
The expression
$function of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
171 | { |
||
172 | if ($part !== null) |
||
173 | { |
||
174 | if ($quote_closed == '`') |
||
175 | { |
||
176 | $args_evaluate[] = $args_count; |
||
177 | } |
||
178 | |||
179 | if (!$quote_closed) |
||
0 ignored issues
–
show
The expression
$quote_closed of type null|string is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
180 | { |
||
181 | # |
||
182 | # end of an unquoted part. |
||
183 | # it might be an integer, a float, or maybe a constant ! |
||
184 | # |
||
185 | |||
186 | switch ($part) |
||
187 | { |
||
188 | case 'true': |
||
189 | case 'TRUE': |
||
0 ignored issues
–
show
CASE statements must be defined using a colon
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces.
There is no need for braces, since each case is terminated by the next switch ($expr) {
case "A": { //wrong
doSomething();
break;
}
case "B": //right
doSomething();
break;
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig. ![]() |
|||
190 | { |
||
191 | $part = true; |
||
192 | } |
||
193 | break; |
||
194 | |||
195 | case 'false': |
||
196 | case 'FALSE': |
||
0 ignored issues
–
show
CASE statements must be defined using a colon
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces.
There is no need for braces, since each case is terminated by the next switch ($expr) {
case "A": { //wrong
doSomething();
break;
}
case "B": //right
doSomething();
break;
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig. ![]() |
|||
197 | { |
||
198 | $part = false; |
||
199 | } |
||
200 | break; |
||
201 | |||
202 | case 'null': |
||
203 | case 'NULL': |
||
0 ignored issues
–
show
CASE statements must be defined using a colon
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces.
There is no need for braces, since each case is terminated by the next switch ($expr) {
case "A": { //wrong
doSomething();
break;
}
case "B": //right
doSomething();
break;
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig. ![]() |
|||
204 | { |
||
205 | $part = null; |
||
206 | } |
||
207 | break; |
||
208 | |||
209 | default: |
||
0 ignored issues
–
show
DEFAULT statements must be defined using a colon
As per the PSR-2 coding standard, default statements should not be wrapped in curly braces. switch ($expr) {
default: { //wrong
doSomething();
break;
}
}
switch ($expr) {
default: //right
doSomething();
break;
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig. ![]() |
|||
210 | { |
||
211 | if (is_numeric($part)) |
||
212 | { |
||
213 | $part = (int) $part; |
||
214 | } |
||
215 | else if (is_float($part)) |
||
216 | { |
||
217 | $part = (float) $part; |
||
218 | } |
||
219 | else |
||
220 | { |
||
221 | $part = constant($part); |
||
222 | } |
||
223 | } |
||
224 | break; |
||
225 | } |
||
226 | } |
||
227 | |||
228 | $args[] = $part; |
||
229 | $args_count++; |
||
230 | |||
231 | $part = null; |
||
232 | } |
||
233 | |||
234 | $quote_closed = null; |
||
235 | |||
236 | if ($c != ')') |
||
237 | { |
||
238 | continue; |
||
239 | } |
||
240 | } |
||
241 | |||
242 | if ($c == ')' && $function) |
||
0 ignored issues
–
show
The expression
$function of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
243 | { |
||
244 | $parts[] = [ |
||
245 | |||
246 | self::TOKEN_TYPE => self::TOKEN_TYPE_FUNCTION, |
||
247 | self::TOKEN_VALUE => $function, |
||
248 | self::TOKEN_ARGS => $args, |
||
249 | self::TOKEN_ARGS_EVALUATE => $args_evaluate |
||
250 | |||
251 | ]; |
||
252 | |||
253 | continue; |
||
254 | } |
||
255 | |||
256 | if ($c == ' ' && $function) |
||
0 ignored issues
–
show
The expression
$function of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
257 | { |
||
258 | continue; |
||
259 | } |
||
260 | |||
261 | $part .= $c; |
||
262 | } |
||
263 | |||
264 | return $parts; |
||
265 | } |
||
266 | |||
267 | protected function evaluate($context, $expression, $tokens, $silent) |
||
268 | { |
||
269 | $expression_path = []; |
||
270 | |||
271 | foreach ($tokens as $i => $part) |
||
272 | { |
||
273 | $identifier = $part[self::TOKEN_VALUE]; |
||
274 | |||
275 | $expression_path[] = $identifier; |
||
276 | |||
277 | switch ($part[self::TOKEN_TYPE]) |
||
278 | { |
||
279 | case self::TOKEN_TYPE_IDENTIFIER: |
||
0 ignored issues
–
show
CASE statements must be defined using a colon
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces.
There is no need for braces, since each case is terminated by the next switch ($expr) {
case "A": { //wrong
doSomething();
break;
}
case "B": //right
doSomething();
break;
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig. ![]() |
|||
280 | { |
||
281 | if (!is_array($context) && !is_object($context)) |
||
282 | { |
||
283 | throw new \InvalidArgumentException(\ICanBoogie\format |
||
284 | ( |
||
285 | 'Unexpected variable type: %type (%value) for %identifier in expression %expression, should be either an array or an object', [ |
||
286 | |||
287 | '%type' => gettype($context), |
||
288 | '%value' => $context, |
||
289 | '%identifier' => $identifier, |
||
290 | '%expression' => $expression |
||
291 | |||
292 | ] |
||
293 | )); |
||
294 | } |
||
295 | |||
296 | $exists = false; |
||
297 | $next_value = $this->extract_value($context, $identifier, $exists); |
||
298 | |||
299 | if (!$exists) |
||
300 | { |
||
301 | if ($silent) |
||
302 | { |
||
303 | return null; |
||
304 | } |
||
305 | |||
306 | throw new ReferenceError(\ICanBoogie\format('Reference to undefined property %path of expression %expression (defined: :keys) in: :value', [ |
||
307 | |||
308 | 'path' => implode('.', $expression_path), |
||
309 | 'expression' => $expression, |
||
310 | 'keys' => implode(', ', $context instanceof Context ? $context->keys() : array_keys((array) $context)), |
||
311 | 'value' => \ICanBoogie\dump($context) |
||
312 | |||
313 | ])); |
||
314 | } |
||
315 | |||
316 | $context = $next_value; |
||
317 | } |
||
318 | break; |
||
319 | |||
320 | case self::TOKEN_TYPE_FUNCTION: |
||
0 ignored issues
–
show
CASE statements must be defined using a colon
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces.
There is no need for braces, since each case is terminated by the next switch ($expr) {
case "A": { //wrong
doSomething();
break;
}
case "B": //right
doSomething();
break;
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig. ![]() |
|||
321 | { |
||
322 | $method = $identifier; |
||
323 | $args = $part[self::TOKEN_ARGS]; |
||
324 | $args_evaluate = $part[self::TOKEN_ARGS_EVALUATE]; |
||
325 | |||
326 | if ($args_evaluate) |
||
327 | { |
||
328 | $this->engine->error('we should evaluate %eval', [ '%eval' => $args_evaluate ]); |
||
329 | } |
||
330 | |||
331 | # |
||
332 | # if value is an object, we check if the object has the method |
||
333 | # |
||
334 | |||
335 | View Code Duplication | if (is_object($context) && method_exists($context, $method)) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
336 | { |
||
337 | $context = call_user_func_array([ $context, $method ], $args); |
||
338 | |||
339 | break; |
||
340 | } |
||
341 | |||
342 | # |
||
343 | # well, the object didn't have the method, |
||
344 | # we check internal functions |
||
345 | # |
||
346 | |||
347 | $callback = $this->engine->functions->find($method); |
||
348 | |||
349 | # |
||
350 | # if no internal function matches, we try string and array functions |
||
351 | # depending on the type of the value |
||
352 | # |
||
353 | |||
354 | if (!$callback) |
||
355 | { |
||
356 | if (is_string($context)) |
||
357 | { |
||
358 | View Code Duplication | if (function_exists('str' . $method)) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
359 | { |
||
360 | $callback = 'str' . $method; |
||
361 | } |
||
362 | else if (function_exists('str_' . $method)) |
||
363 | { |
||
364 | $callback = 'str_' . $method; |
||
365 | } |
||
366 | } |
||
367 | else if (is_array($context) || is_object($context)) |
||
368 | { |
||
369 | View Code Duplication | if (function_exists('ICanBoogie\array_' . $method)) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
370 | { |
||
371 | $callback = 'ICanBoogie\array_' . $method; |
||
372 | } |
||
373 | else if (function_exists('array_' . $method)) |
||
374 | { |
||
375 | $callback = 'array_' . $method; |
||
376 | } |
||
377 | } |
||
378 | } |
||
379 | |||
380 | # |
||
381 | # our last hope is to try the function "as is" |
||
382 | # |
||
383 | |||
384 | if (!$callback) |
||
0 ignored issues
–
show
The expression
$callback of type false|string is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
385 | { |
||
386 | if (function_exists($method)) |
||
387 | { |
||
388 | $callback = $method; |
||
389 | } |
||
390 | } |
||
391 | |||
392 | View Code Duplication | if (!$callback) |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
393 | { |
||
394 | if (is_object($context) && method_exists($context, '__call')) |
||
395 | { |
||
396 | $context = call_user_func_array([ $context, $method ], $args); |
||
397 | |||
398 | break; |
||
399 | } |
||
400 | } |
||
401 | |||
402 | # |
||
403 | # |
||
404 | # |
||
405 | |||
406 | if (!$callback) |
||
407 | { |
||
408 | throw new \Exception(\ICanBoogie\format('Unknown method %method for expression %expression.', [ |
||
409 | |||
410 | '%method' => $method, |
||
411 | '%expression' => $expression |
||
412 | |||
413 | ])); |
||
414 | } |
||
415 | |||
416 | # |
||
417 | # create evaluation |
||
418 | # |
||
419 | |||
420 | array_unshift($args, $context); |
||
421 | |||
422 | if (PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 2)) |
||
423 | { |
||
424 | if ($callback == 'array_shift') |
||
425 | { |
||
426 | $context = array_shift($context); |
||
427 | } |
||
428 | else |
||
429 | { |
||
430 | $context = call_user_func_array($callback, $args); |
||
431 | } |
||
432 | } |
||
433 | else |
||
434 | { |
||
435 | $context = call_user_func_array($callback, $args); |
||
436 | } |
||
437 | } |
||
438 | break; |
||
439 | } |
||
440 | } |
||
441 | |||
442 | return $context; |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Extract a value from a container. |
||
447 | * |
||
448 | * @param mixed $container A value can be extracted from the following containers, in that |
||
449 | * order: |
||
450 | * |
||
451 | * - An array, where the `$identifier` key exists. |
||
452 | * - An object implementing the `$identifier` property. |
||
453 | * - An object implementing `has_property()` which is used to determine if the object |
||
454 | * implements the property. |
||
455 | * - An object implementing `ArrayAccess`, where the `$identifier` offset exists. |
||
456 | * - Finaly, an object implementing `__get()`. |
||
457 | * |
||
458 | * @param string $identifier The identifier of the value to extract. |
||
459 | * @param bool $exists `true` when the value was extracted, `false` otherwise. |
||
460 | * |
||
461 | * @return mixed The extracted value. |
||
462 | */ |
||
463 | protected function extract_value($container, $identifier, &$exists=false) |
||
464 | { |
||
465 | $exists = false; |
||
466 | |||
467 | # array |
||
468 | |||
469 | if (is_array($container)) |
||
470 | { |
||
471 | $exists = array_key_exists($identifier, $container); |
||
472 | |||
473 | return $exists ? $container[$identifier] : null; |
||
474 | } |
||
475 | |||
476 | # object |
||
477 | |||
478 | $exists = property_exists($container, $identifier); |
||
479 | |||
480 | if ($exists) |
||
481 | { |
||
482 | return $container->$identifier; |
||
483 | } |
||
484 | |||
485 | if (method_exists($container, 'has_property')) |
||
486 | { |
||
487 | $exists = $container->has_property($identifier); |
||
488 | |||
489 | return $exists ? $container->$identifier : null; |
||
490 | } |
||
491 | |||
492 | if ($container instanceof \ArrayAccess) |
||
493 | { |
||
494 | $exists = $container->offsetExists($identifier); |
||
495 | |||
496 | if ($exists) |
||
497 | { |
||
498 | return $container[$identifier]; |
||
499 | } |
||
500 | } |
||
501 | |||
502 | if (method_exists($container, '__get')) |
||
503 | { |
||
504 | $exists = true; |
||
505 | |||
506 | return $container->$identifier; |
||
507 | } |
||
508 | |||
509 | return null; |
||
510 | } |
||
511 | } |
||
512 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: