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 | namespace PHPDaemon\Config; |
||
3 | |||
4 | use PHPDaemon\Config\Entry\Generic; |
||
5 | use PHPDaemon\Core\Daemon; |
||
6 | use PHPDaemon\Core\Debug; |
||
7 | use PHPDaemon\Exceptions\InfiniteRecursion; |
||
8 | |||
9 | /** |
||
10 | * Config parser |
||
11 | * |
||
12 | * @package Core |
||
13 | * @subpackage Config |
||
14 | * |
||
15 | * @author Vasily Zorin <[email protected]> |
||
16 | */ |
||
17 | class Parser |
||
18 | { |
||
19 | use \PHPDaemon\Traits\ClassWatchdog; |
||
20 | use \PHPDaemon\Traits\StaticObjectWatchdog; |
||
21 | |||
22 | /** |
||
23 | * State: standby |
||
24 | */ |
||
25 | const T_ALL = 1; |
||
26 | /** |
||
27 | * State: comment |
||
28 | */ |
||
29 | const T_COMMENT = 2; |
||
30 | /** |
||
31 | * State: variable definition block |
||
32 | */ |
||
33 | const T_VAR = 3; |
||
34 | /** |
||
35 | * Single-quoted string |
||
36 | */ |
||
37 | const T_STRING = 4; |
||
38 | |||
39 | /** |
||
40 | * Double-quoted |
||
41 | */ |
||
42 | const T_STRING_DOUBLE = 5; |
||
43 | |||
44 | /** |
||
45 | * Block |
||
46 | */ |
||
47 | const T_BLOCK = 6; |
||
48 | |||
49 | /** |
||
50 | * Value defined by constant (keyword) or number |
||
51 | */ |
||
52 | const T_CVALUE = 7; |
||
53 | |||
54 | /** |
||
55 | * Config file path |
||
56 | * @var string |
||
57 | */ |
||
58 | protected $file; |
||
59 | |||
60 | /** |
||
61 | * Current line number |
||
62 | * @var number |
||
63 | */ |
||
64 | protected $line = 1; |
||
65 | |||
66 | /** |
||
67 | * Current column number |
||
68 | * @var number |
||
69 | */ |
||
70 | protected $col = 1; |
||
71 | |||
72 | /** |
||
73 | * Pointer (current offset) |
||
74 | * @var integer |
||
75 | */ |
||
76 | protected $p = 0; |
||
77 | |||
78 | /** |
||
79 | * State stack |
||
80 | * @var array |
||
81 | */ |
||
82 | protected $state = []; |
||
83 | |||
84 | /** |
||
85 | * Target object |
||
86 | * @var object |
||
87 | */ |
||
88 | protected $target; |
||
89 | |||
90 | /** |
||
91 | * Erroneous? |
||
92 | * @var boolean |
||
93 | */ |
||
94 | protected $erroneous = false; |
||
95 | |||
96 | /** |
||
97 | * Callbacks |
||
98 | * @var array |
||
99 | */ |
||
100 | protected $tokens; |
||
101 | |||
102 | /** |
||
103 | * File length |
||
104 | * @var integer |
||
105 | */ |
||
106 | protected $length; |
||
107 | |||
108 | /** |
||
109 | * Revision |
||
110 | * @var integer |
||
111 | */ |
||
112 | protected $revision; |
||
113 | |||
114 | /** |
||
115 | * Contents of config file |
||
116 | * @var string |
||
117 | */ |
||
118 | protected $data; |
||
119 | |||
120 | /** |
||
121 | * Parse stack |
||
122 | * @var array |
||
123 | */ |
||
124 | protected static $stack = []; |
||
125 | |||
126 | /** |
||
127 | * Erroneous? |
||
128 | * @return boolean |
||
129 | */ |
||
130 | public function isErroneous() |
||
131 | { |
||
132 | return $this->erroneous; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Parse config file |
||
137 | * @param string File path |
||
138 | * @param _Object Target |
||
139 | * @param boolean Included? Default is false |
||
140 | * @return \PHPDaemon\Config\Parser |
||
141 | */ |
||
142 | public static function parse($file, $target, $included = false) |
||
143 | { |
||
144 | if (in_array($file, static::$stack)) { |
||
145 | throw new InfiniteRecursion; |
||
146 | } |
||
147 | |||
148 | static::$stack[] = $file; |
||
149 | $parser = new static($file, $target, $included); |
||
150 | array_pop(static::$stack); |
||
151 | return $parser; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Constructor |
||
156 | * @return void |
||
0 ignored issues
–
show
|
|||
157 | */ |
||
158 | protected function __construct($file, $target, $included = false) |
||
159 | { |
||
160 | $this->file = $file; |
||
161 | $this->target = $target; |
||
162 | $this->revision = ++_Object::$lastRevision; |
||
163 | $this->data = file_get_contents($file); |
||
164 | |||
165 | if (substr($this->data, 0, 2) === '#!') { |
||
166 | if (!is_executable($file)) { |
||
167 | $this->raiseError('Shebang (#!) detected in the first line, but file hasn\'t +x mode.'); |
||
168 | return; |
||
169 | } |
||
170 | $this->data = shell_exec($file); |
||
171 | } |
||
172 | |||
173 | $this->data = str_replace("\r", '', $this->data); |
||
174 | $this->length = mb_orig_strlen($this->data); |
||
175 | $this->state[] = [static::T_ALL, $this->target]; |
||
176 | $this->tokens = [ |
||
177 | static::T_COMMENT => function ($c) { |
||
178 | if ($c === "\n") { |
||
179 | array_pop($this->state); |
||
180 | } |
||
181 | }, |
||
182 | static::T_STRING_DOUBLE => function ($q) { |
||
183 | $str = ''; |
||
184 | ++$this->p; |
||
185 | |||
186 | for (; $this->p < $this->length; ++$this->p) { |
||
187 | $c = $this->getCurrentChar(); |
||
188 | |||
189 | if ($c === $q) { |
||
190 | ++$this->p; |
||
191 | break; |
||
192 | } elseif ($c === '\\') { |
||
193 | next: |
||
194 | $n = $this->getNextChar(); |
||
195 | if ($n === $q) { |
||
196 | $str .= $q; |
||
197 | ++$this->p; |
||
198 | } elseif (ctype_digit($n)) { |
||
199 | $def = $n; |
||
200 | ++$this->p; |
||
201 | View Code Duplication | for (; $this->p < min($this->length, $this->p + 2); ++$this->p) { |
|
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. ![]() |
|||
202 | $n = $this->getNextChar(); |
||
203 | if (!ctype_digit($n)) { |
||
204 | break; |
||
205 | } |
||
206 | $def .= $n; |
||
207 | } |
||
208 | $str .= chr((int)$def); |
||
209 | } elseif (($n === 'x') || ($n === 'X')) { |
||
210 | $def = $n; |
||
211 | ++$this->p; |
||
212 | View Code Duplication | for (; $this->p < min($this->length, $this->p + 2); ++$this->p) { |
|
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. ![]() |
|||
213 | $n = $this->getNextChar(); |
||
214 | if (!ctype_xdigit($n)) { |
||
215 | break; |
||
216 | } |
||
217 | $def .= $n; |
||
218 | } |
||
219 | $str .= chr((int)hexdec($def)); |
||
220 | } else { |
||
221 | $str .= $c; |
||
222 | } |
||
223 | } else { |
||
224 | $str .= $c; |
||
225 | } |
||
226 | } |
||
227 | |||
228 | if ($this->p >= $this->length) { |
||
229 | $this->raiseError('Unexpected End-Of-File.'); |
||
230 | } |
||
231 | return $str; |
||
232 | }, |
||
233 | static::T_STRING => function ($q) { |
||
234 | $str = ''; |
||
235 | ++$this->p; |
||
236 | |||
237 | for (; $this->p < $this->length; ++$this->p) { |
||
238 | $c = $this->getCurrentChar(); |
||
239 | |||
240 | if ($c === $q) { |
||
241 | ++$this->p; |
||
242 | break; |
||
243 | } elseif ($c === '\\') { |
||
244 | if ($this->getNextChar() === $q) { |
||
245 | $str .= $q; |
||
246 | ++$this->p; |
||
247 | } else { |
||
248 | $str .= $c; |
||
249 | } |
||
250 | } else { |
||
251 | $str .= $c; |
||
252 | } |
||
253 | } |
||
254 | |||
255 | if ($this->p >= $this->length) { |
||
256 | $this->raiseError('Unexpected End-Of-File.'); |
||
257 | } |
||
258 | return $str; |
||
259 | }, |
||
260 | static::T_ALL => function ($c) { |
||
261 | if (ctype_space($c)) { |
||
262 | } elseif ($c === '#') { |
||
263 | $this->state[] = [static::T_COMMENT]; |
||
264 | } elseif ($c === '}') { |
||
265 | if (sizeof($this->state) > 1) { |
||
266 | $this->purgeScope($this->getCurrentScope()); |
||
267 | array_pop($this->state); |
||
268 | } else { |
||
269 | $this->raiseError('Unexpected \'}\''); |
||
270 | } |
||
271 | } elseif (ctype_alnum($c) || $c === '\\') { |
||
272 | $elements = ['']; |
||
273 | $elTypes = [null]; |
||
274 | $i = 0; |
||
275 | $tokenType = 0; |
||
276 | $newLineDetected = null; |
||
277 | |||
278 | for (; $this->p < $this->length; ++$this->p) { |
||
279 | $prePoint = [$this->line, $this->col - 1]; |
||
280 | $c = $this->getCurrentChar(); |
||
281 | |||
282 | if (ctype_space($c) || $c === '=' || $c === ',') { |
||
283 | if ($c === "\n") { |
||
284 | $newLineDetected = $prePoint; |
||
285 | } |
||
286 | if ($elTypes[$i] !== null) { |
||
287 | ++$i; |
||
288 | $elTypes[$i] = null; |
||
289 | } |
||
290 | } elseif ($c === '\'') { |
||
291 | if ($elTypes[$i] !== null) { |
||
292 | $this->raiseError('Unexpected T_STRING.'); |
||
293 | } |
||
294 | |||
295 | $string = $this->token(static::T_STRING, $c); |
||
296 | --$this->p; |
||
297 | |||
298 | View Code Duplication | if ($elTypes[$i] === null) { |
|
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. ![]() |
|||
299 | $elements[$i] = $string; |
||
300 | $elTypes[$i] = static::T_STRING; |
||
301 | } |
||
302 | } elseif ($c === '"') { |
||
303 | if ($elTypes[$i] !== null) { |
||
304 | $this->raiseError('Unexpected T_STRING_DOUBLE.'); |
||
305 | } |
||
306 | |||
307 | $string = $this->token(static::T_STRING_DOUBLE, $c); |
||
308 | --$this->p; |
||
309 | |||
310 | View Code Duplication | if ($elTypes[$i] === null) { |
|
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. ![]() |
|||
311 | $elements[$i] = $string; |
||
312 | $elTypes[$i] = static::T_STRING_DOUBLE; |
||
313 | } |
||
314 | } elseif ($c === '}') { |
||
315 | $this->raiseError('Unexpected \'}\' instead of \';\' or \'{\''); |
||
316 | } elseif ($c === ';') { |
||
317 | if ($newLineDetected) { |
||
318 | $this->raiseError('Unexpected new-line instead of \';\'', 'notice', $newLineDetected[0], |
||
319 | $newLineDetected[1]); |
||
320 | } |
||
321 | $tokenType = static::T_VAR; |
||
322 | break; |
||
323 | } elseif ($c === '{') { |
||
324 | $tokenType = static::T_BLOCK; |
||
325 | break; |
||
326 | } else { |
||
327 | if ($elTypes[$i] === static::T_STRING) { |
||
328 | $this->raiseError('Unexpected T_CVALUE.'); |
||
329 | } else { |
||
330 | if (!isset($elements[$i])) { |
||
331 | $elements[$i] = ''; |
||
332 | } |
||
333 | |||
334 | $elements[$i] .= $c; |
||
335 | $elTypes[$i] = static::T_CVALUE; |
||
336 | } |
||
337 | } |
||
338 | } |
||
339 | foreach ($elTypes as $k => $v) { |
||
340 | if (static::T_CVALUE === $v) { |
||
341 | if (ctype_digit($elements[$k])) { |
||
342 | $elements[$k] = (int)$elements[$k]; |
||
343 | } elseif (is_numeric($elements[$k])) { |
||
344 | $elements[$k] = (float)$elements[$k]; |
||
345 | } else { |
||
346 | $l = strtolower($elements[$k]); |
||
347 | |||
348 | if (($l === 'true') || ($l === 'on')) { |
||
349 | $elements[$k] = true; |
||
350 | } elseif (($l === 'false') || ($l === 'off')) { |
||
351 | $elements[$k] = false; |
||
352 | } elseif ($l === 'null') { |
||
353 | $elements[$k] = null; |
||
354 | } |
||
355 | } |
||
356 | } |
||
357 | } |
||
358 | if ($tokenType === 0) { |
||
359 | $this->raiseError('Expected \';\' or \'{\''); |
||
360 | } elseif ($tokenType === static::T_VAR) { |
||
361 | $name = str_replace('-', '', strtolower($elements[0])); |
||
362 | if (sizeof($elements) > 2) { |
||
363 | $value = array_slice($elements, 1); |
||
364 | } else { |
||
365 | $value = isset($elements[1]) ? $elements[1] : null; |
||
366 | } |
||
367 | $scope = $this->getCurrentScope(); |
||
368 | |||
369 | if ($name === 'include') { |
||
370 | if (!is_array($value)) { |
||
371 | $value = [$value]; |
||
372 | } |
||
373 | foreach ($value as $path) { |
||
374 | if (substr($path, 0, 1) !== '/') { |
||
375 | $path = 'conf/' . $path; |
||
376 | } |
||
377 | $files = glob($path); |
||
378 | if ($files) { |
||
0 ignored issues
–
show
The expression
$files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
379 | foreach ($files as $fn) { |
||
380 | try { |
||
381 | static::parse($fn, $scope, true); |
||
382 | } catch (InfiniteRecursion $e) { |
||
383 | $this->raiseError('Cannot include \'' . $fn . '\' as a part of itself, it may cause an infinite recursion.'); |
||
384 | } |
||
385 | } |
||
386 | } |
||
387 | } |
||
388 | } else { |
||
389 | if (sizeof($elements) === 1) { |
||
390 | $value = true; |
||
391 | $elements[1] = true; |
||
392 | $elTypes[1] = static::T_CVALUE; |
||
393 | } elseif ($value === null) { |
||
394 | $value = null; |
||
395 | $elements[1] = null; |
||
396 | $elTypes[1] = static::T_CVALUE; |
||
397 | } |
||
398 | |||
399 | if (isset($scope->{$name})) { |
||
400 | if ($scope->{$name}->source !== 'cmdline') { |
||
401 | if (($elTypes[1] === static::T_CVALUE) && is_string($value)) { |
||
402 | $scope->{$name}->pushHumanValue($value); |
||
403 | } else { |
||
404 | $scope->{$name}->pushValue($value); |
||
405 | } |
||
406 | $scope->{$name}->source = 'config'; |
||
407 | $scope->{$name}->revision = $this->revision; |
||
408 | } |
||
409 | } elseif ($scope instanceof Section) { |
||
410 | $scope->{$name} = new Generic(); |
||
411 | $scope->{$name}->source = 'config'; |
||
412 | $scope->{$name}->revision = $this->revision; |
||
413 | $scope->{$name}->pushValue($value); |
||
414 | $scope->{$name}->setValueType($value); |
||
415 | } else { |
||
416 | $this->raiseError('Unrecognized parameter \'' . $name . '\''); |
||
417 | } |
||
418 | } |
||
419 | } elseif ($tokenType === static::T_BLOCK) { |
||
420 | $scope = $this->getCurrentScope(); |
||
421 | $sectionName = implode('-', $elements); |
||
422 | $sectionName = strtr($sectionName, '-. ', ':::'); |
||
423 | if (!isset($scope->{$sectionName})) { |
||
424 | $scope->{$sectionName} = new Section; |
||
425 | } |
||
426 | $scope->{$sectionName}->source = 'config'; |
||
427 | $scope->{$sectionName}->revision = $this->revision; |
||
428 | $this->state[] = [ |
||
429 | static::T_ALL, |
||
430 | $scope->{$sectionName}, |
||
431 | ]; |
||
432 | } |
||
433 | } else { |
||
434 | $this->raiseError('Unexpected char \'' . Debug::exportBytes($c) . '\''); |
||
435 | } |
||
436 | } |
||
437 | ]; |
||
438 | |||
439 | for (; $this->p < $this->length; ++$this->p) { |
||
440 | $c = $this->getCurrentChar(); |
||
441 | $e = end($this->state); |
||
442 | $this->token($e[0], $c); |
||
443 | } |
||
444 | if (!$included) { |
||
445 | $this->purgeScope($this->target); |
||
446 | } |
||
447 | |||
448 | if (Daemon::$config->verbosetty->value) { |
||
449 | Daemon::log('Loaded config file: ' . escapeshellarg($file)); |
||
450 | } |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * Removes old config parts after updating. |
||
455 | * @return void |
||
456 | */ |
||
457 | protected function purgeScope($scope) |
||
458 | { |
||
459 | foreach ($scope as $name => $obj) { |
||
460 | if ($obj instanceof Generic) { |
||
461 | if ($obj->source === 'config' && ($obj->revision < $this->revision)) { |
||
462 | if (!$obj->resetToDefault()) { |
||
463 | unset($scope->{$name}); |
||
464 | } |
||
465 | } |
||
466 | } elseif ($obj instanceof Section) { |
||
467 | if ($obj->source === 'config' && ($obj->revision < $this->revision)) { |
||
468 | if ($obj->count() === 0) { |
||
469 | unset($scope->{$name}); |
||
470 | } elseif (isset($obj->enable)) { |
||
471 | $obj->enable->setValue(false); |
||
472 | } |
||
473 | } |
||
474 | } |
||
475 | } |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Returns current variable scope |
||
480 | * @return object Scope. |
||
481 | */ |
||
482 | public function getCurrentScope() |
||
483 | { |
||
484 | $e = end($this->state); |
||
485 | |||
486 | return $e[1]; |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Raises error message. |
||
491 | * @param string Message. |
||
492 | * @param string Level. |
||
493 | * @param string $msg |
||
494 | * @return void |
||
495 | */ |
||
496 | public function raiseError($msg, $level = 'emerg', $line = null, $col = null) |
||
497 | { |
||
498 | if ($level === 'emerg') { |
||
499 | $this->erroneous = true; |
||
500 | } |
||
501 | if ($line === null) { |
||
502 | $line = $this->line; |
||
503 | } |
||
504 | if ($col === null) { |
||
505 | $col = $this->col - 1; |
||
506 | } |
||
507 | |||
508 | Daemon::log('[conf#' . $level . '][' . $this->file . ' L:' . $line . ' C: ' . $col . '] ' . $msg); |
||
509 | } |
||
510 | |||
511 | /** |
||
512 | * Executes token server. |
||
513 | * @param string $c |
||
514 | * @return mixed|void |
||
515 | */ |
||
516 | protected function token($token, $c) |
||
517 | { |
||
518 | return $this->tokens[$token]($c); |
||
519 | } |
||
520 | |||
521 | /** |
||
522 | * Current character. |
||
523 | * @return string Character. |
||
524 | */ |
||
525 | protected function getCurrentChar() |
||
526 | { |
||
527 | $c = substr($this->data, $this->p, 1); |
||
528 | |||
529 | if ($c === "\n") { |
||
530 | ++$this->line; |
||
531 | $this->col = 1; |
||
532 | } else { |
||
533 | ++$this->col; |
||
534 | } |
||
535 | |||
536 | return $c; |
||
537 | } |
||
538 | |||
539 | /** |
||
540 | * Returns next character. |
||
541 | * @return string Character. |
||
542 | */ |
||
543 | protected function getNextChar() |
||
544 | { |
||
545 | return substr($this->data, $this->p + 1, 1); |
||
546 | } |
||
547 | |||
548 | /** |
||
549 | * Rewinds the pointer back. |
||
550 | * @param integer Number of characters to rewind back. |
||
551 | * @return void |
||
552 | */ |
||
553 | protected function rewind($n) |
||
554 | { |
||
555 | $this->p -= $n; |
||
556 | } |
||
557 | } |
||
558 |
Adding a
@return
annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.Please refer to the PHP core documentation on constructors.