Issues (393)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/Evaluator.php (20 issues)

Upgrade to new PHP Analysis Engine

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
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
110
				{
111
					$quote = null;
112
					$quote_closed = $c;
113
114
					if ($function)
0 ignored issues
show
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
115
					{
116
						continue;
117
					}
118
				}
119
				else if (!$quote)
0 ignored issues
show
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
120
				{
121
					$quote = $c;
122
123
					if ($function)
0 ignored issues
show
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
124
					{
125
						continue;
126
					}
127
				}
128
			}
129
130
			if ($quote)
0 ignored issues
show
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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 break.

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.

Loading history...
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 break.

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.

Loading history...
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 break.

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.

Loading history...
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.

Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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 break.

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.

Loading history...
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 break.

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.

Loading history...
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.

Loading history...
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.

Loading history...
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.

Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch 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:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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.

Loading history...
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