Issues (1507)

Security Analysis    not enabled

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.

PHPDaemon/Config/Parser.php (6 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
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
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

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.

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

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

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

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

Loading history...
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
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

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