Issues (37)

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.

src/Yaml/Inline.php (9 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
 * Scabbia2 Yaml Component
4
 * https://github.com/eserozvataf/scabbia2
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 *
9
 * @link        https://github.com/eserozvataf/scabbia2-yaml for the canonical source repository
10
 * @copyright   2010-2016 Eser Ozvataf. (http://eser.ozvataf.com/)
11
 * @license     http://www.apache.org/licenses/LICENSE-2.0 - Apache License, Version 2.0
12
 *
13
 * -------------------------
14
 * Portions of this code are from Symfony YAML Component under the MIT license.
15
 *
16
 * (c) Fabien Potencier <[email protected]>
17
 *
18
 * For the full copyright and license information, please view the LICENSE-MIT
19
 * file that was distributed with this source code.
20
 *
21
 * Modifications made:
22
 * - Scabbia Framework code styles applied.
23
 * - All dump methods are moved under Dumper class.
24
 * - Redundant classes removed.
25
 * - Namespace changed.
26
 * - Tests ported to Scabbia2.
27
 * - Encoding checks removed.
28
 */
29
30
namespace Scabbia\Yaml;
31
32
use Scabbia\Yaml\Escaper;
33
use Scabbia\Yaml\ParseException;
34
35
/**
36
 * Inline implements a YAML parser for the YAML inline syntax
37
 *
38
 * @package     Scabbia\Yaml
39
 * @author      Fabien Potencier <[email protected]>
40
 * @author      Eser Ozvataf <[email protected]>
41
 * @since       2.0.0
42
 */
43
class Inline
44
{
45
    /** @type string REGEX_QUOTED_STRING a regular expression pattern to match quoted strings */
46
    const REGEX_QUOTED_STRING = "(?:\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|'([^']*(?:''[^']*)*)')";
47
48
49
    /**
50
     * Converts a YAML string to a PHP array
51
     *
52
     * @param string  $value                  A YAML string
53
     * @param array   $references             Mapping of variable names to values
54
     *
55
     * @throws ParseException If the YAML is not valid
56
     * @return array A PHP array representing the YAML string
57
     */
58
    public static function parse($value, $references = [])
59
    {
60
        $value = trim($value);
61
62
        if (strlen($value) === 0) {
63
            return "";
64
        }
65
66
        $i = 0;
67
        if ($value[0] === "[") {
68
            $result = self::parseSequence($value, $i, $references);
69
            ++$i;
70
        } elseif ($value[0] === "{") {
71
            $result = self::parseMapping($value, $i, $references);
72
            ++$i;
73
        } else {
74
            $result = self::parseScalar($value, null, ["\"", "'"], $i, true, $references);
75
        }
76
77
        // some comments are allowed at the end
78
        if (preg_replace("/\\s+#.*$/A", "", substr($value, $i))) {
79
            throw new ParseException(sprintf("Unexpected characters near \"%s\".", substr($value, $i)));
80
        }
81
82
        return $result;
83
    }
84
85
    /**
86
     * Parses a scalar to a YAML string
87
     *
88
     * @param scalar $scalar
89
     * @param string $delimiters
90
     * @param array  $stringDelimiters
91
     * @param int    &$i
92
     * @param bool   $evaluate
93
     * @param array  $references
94
     *
95
     * @throws ParseException When malformed inline YAML string is parsed
96
     * @return string A YAML string
97
     *
98
     * @internal
99
     */
100
    public static function parseScalar(
101
        $scalar,
102
        $delimiters = null,
103
        array $stringDelimiters = ["\"", "'"],
104
        &$i = 0,
105
        $evaluate = true,
106
        $references = []
107
    ) {
108
        if (in_array($scalar[$i], $stringDelimiters)) {
109
            // quoted scalar
110
            $output = self::parseQuotedScalar($scalar, $i);
111
112
            if ($delimiters !== null) {
113
                $tmp = ltrim(substr($scalar, $i), " ");
114
                if (!in_array($tmp[0], $delimiters)) {
115
                    throw new ParseException(sprintf("Unexpected characters (%s).", substr($scalar, $i)));
116
                }
117
            }
118
119
            return $output;
120
        }
121
122
        // "normal" string
123
        if (!$delimiters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delimiters of type string|null 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...
124
            $output = substr($scalar, $i);
125
            $i += strlen($output);
126
127
            // remove comments
128
            if (preg_match("/[ \t]+#/", $output, $match, PREG_OFFSET_CAPTURE)) {
129
                $output = substr($output, 0, $match[0][1]);
130
            }
131
        } elseif (preg_match("/^(.+?)(" . implode("|", $delimiters) . ")/", substr($scalar, $i), $match)) {
132
            $output = $match[1];
133
            $i += strlen($output);
134
        } else {
135
            throw new ParseException(sprintf("Malformed inline YAML string (%s).", $scalar));
136
        }
137
138
        // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
139
        if ($output && ($output[0] === "@" || $output[0] === "`" || $output[0] === "|" || $output[0] === ">")) {
140
            throw new ParseException(sprintf("The reserved indicator \"%s\" cannot start a plain scalar; you need to quote the scalar.", $output[0]));
141
        }
142
143
        if ($evaluate) {
144
            return self::evaluateScalar($output, $references);
145
        }
146
147
        return $output;
148
    }
149
150
    /**
151
     * Parses a quoted scalar to YAML
152
     *
153
     * @param string $scalar
154
     * @param int    &$i
155
     *
156
     * @throws ParseException When malformed inline YAML string is parsed
157
     * @return string A YAML string
158
     */
159
    protected static function parseQuotedScalar($scalar, &$i)
160
    {
161
        if (!preg_match("/" . self::REGEX_QUOTED_STRING . "/Au", substr($scalar, $i), $match)) {
162
            throw new ParseException(sprintf("Malformed inline YAML string (%s).", substr($scalar, $i)));
163
        }
164
165
        $output = substr($match[0], 1, strlen($match[0]) - 2);
166
167
        $escaper = new Escaper();
168
        if ($scalar[$i] == "\"") {
169
            $output = $escaper->unescapeDoubleQuotedString($output);
170
        } else {
171
            $output = $escaper->unescapeSingleQuotedString($output);
172
        }
173
174
        $i += strlen($match[0]);
175
176
        return $output;
177
    }
178
179
    /**
180
     * Parses a sequence to a YAML string
181
     *
182
     * @param string $sequence
183
     * @param int    &$i
184
     * @param array  $references
185
     *
186
     * @throws ParseException When malformed inline YAML string is parsed
187
     * @return string A YAML string
188
     */
189
    protected static function parseSequence($sequence, &$i = 0, $references = [])
190
    {
191
        $output = [];
192
        $len = strlen($sequence);
193
        ++$i;
194
195
        // [foo, bar, ...]
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
196
        while ($i < $len) {
197
            if ($sequence[$i] === "[") {
198
                // nested sequence
199
                $output[] = self::parseSequence($sequence, $i, $references);
200
            } elseif ($sequence[$i] === "{") {
201
                // nested mapping
202
                $output[] = self::parseMapping($sequence, $i, $references);
203
            } elseif ($sequence[$i] === "]") {
204
                return $output;
205
            } elseif ($sequence[$i] !== "," && $sequence[$i] !== " ") {
206
                $isQuoted = in_array($sequence[$i], ["\"", "'"]);
207
                $value = self::parseScalar($sequence, [",", "]"], ["\"", "'"], $i, true, $references);
0 ignored issues
show
array(',', ']') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
208
209
                // the value can be an array if a reference has been resolved to an array var
210
                if (!is_array($value) && !$isQuoted && strpos($value, ": ") !== false) {
211
                    // embedded mapping?
212
                    try {
213
                        $pos = 0;
214
                        $value = self::parseMapping("{" . $value . "}", $pos, $references);
215
                    } catch (\InvalidArgumentException $e) {
216
                        // no, it's not
217
                    }
218
                }
219
220
                $output[] = $value;
221
                --$i;
222
            }
223
224
            ++$i;
225
        }
226
227
        throw new ParseException(sprintf("Malformed inline YAML string %s", $sequence));
228
    }
229
230
    /**
231
     * Parses a mapping to a YAML string
232
     *
233
     * @param string $mapping
234
     * @param int    &$i
235
     * @param array  $references
236
     *
237
     * @throws ParseException When malformed inline YAML string is parsed
238
     * @return string A YAML string
239
     */
240
    protected static function parseMapping($mapping, &$i = 0, $references = [])
241
    {
242
        $output = [];
243
        $len = strlen($mapping);
244
        ++$i;
245
246
        // {foo: bar, bar:foo, ...}
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
247
        while ($i < $len) {
248
            if ($mapping[$i] === " " || $mapping[$i] === ",") {
249
                ++$i;
250
                continue;
251
            } elseif ($mapping[$i] === "}") {
252
                return $output;
253
            }
254
255
            // key
256
            $key = self::parseScalar($mapping, [":", " "], ["\"", "'"], $i, false);
0 ignored issues
show
array(':', ' ') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
257
258
            // value
259
            $done = false;
260
            while ($i < $len) {
261
                if ($mapping[$i] === "[") {
262
                    // nested sequence
263
                    $output[$key] = self::parseSequence($mapping, $i, $references);
264
                    $done = true;
265
                } elseif ($mapping[$i] === "{") {
266
                    // nested mapping
267
                    $output[$key] = self::parseMapping($mapping, $i, $references);
268
                    $done = true;
269
                } elseif ($mapping[$i] !== ":" && $mapping[$i] !== " ") {
270
                    $output[$key] = self::parseScalar($mapping, [",", "}"], ["\"", "'"], $i, true, $references);
0 ignored issues
show
array(',', '}') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
271
                    $done = true;
272
                    --$i;
273
                }
274
275
                ++$i;
276
277
                if ($done) {
278
                    continue 2;
279
                }
280
            }
281
        }
282
283
        throw new ParseException(sprintf("Malformed inline YAML string %s", $mapping));
284
    }
285
286
    /**
287
     * Evaluates scalars and replaces magic values
288
     *
289
     * @param string $scalar
290
     * @param array  $references
291
     *
292
     * @throws ParseException when a reference could not be resolved
293
     * @return string A YAML string
294
     */
295
    protected static function evaluateScalar($scalar, $references = [])
296
    {
297
        $scalar = trim($scalar);
298
        $scalarLower = strtolower($scalar);
299
300
        if (strpos($scalar, "*") === 0) {
301 View Code Duplication
            if (($pos = strpos($scalar, "#")) !== false) {
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...
302
                $value = substr($scalar, 1, $pos - 2);
303
            } else {
304
                $value = substr($scalar, 1);
305
            }
306
307
            // an unquoted *
308
            if ($value === false || $value === "") {
309
                throw new ParseException("A reference must contain at least one character.");
310
            }
311
312
            if (!array_key_exists($value, $references)) {
313
                throw new ParseException(sprintf("Reference \"%s\" does not exist.", $value));
314
            }
315
316
            return $references[$value];
317
        }
318
319
        if ($scalarLower === "null" || $scalar === "" || $scalar === "~") {
320
            return null;
321
        } elseif ($scalarLower === "true") {
322
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type documented by Scabbia\Yaml\Inline::evaluateScalar of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
323
        } elseif ($scalarLower === "false") {
324
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Scabbia\Yaml\Inline::evaluateScalar of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
325
        } elseif ($scalar[0] === "+" || $scalar[0] === "-" || $scalar[0] === "." || $scalar[0] === "!" ||
326
            is_numeric($scalar[0])) {
327
            // Optimise for returning strings.
328
            if (strpos($scalar, "!str") === 0) {
329
                return (string)substr($scalar, 5);
330
            } elseif (strpos($scalar, "! ") === 0) {
331
                return (int)self::parseScalar(substr($scalar, 2));
332
            } elseif (strpos($scalar, "!!php/object:") === 0) {
333
                return unserialize(substr($scalar, 13));
334
            } elseif (strpos($scalar, "!!float ") === 0) {
335
                return (float)substr($scalar, 8);
336
            } elseif (ctype_digit($scalar)) {
337
                $raw = $scalar;
338
                $cast = (int)$scalar;
339
340
                return $scalar[0] == "0" ? octdec($scalar) : (((string)$raw == (string)$cast) ? $cast : $raw);
341
            } elseif ($scalar[0] === "-" && ctype_digit(substr($scalar, 1))) {
342
                $raw = $scalar;
343
                $cast = (int)$scalar;
344
345
                return $scalar[1] == "0" ? octdec($scalar) : (((string)$raw == (string)$cast) ? $cast : $raw);
346
            } elseif (is_numeric($scalar) || preg_match(self::getHexRegex(), $scalar)) {
347
                return $scalar[0] . $scalar[1] == "0x" ? hexdec($scalar) : (float)$scalar;
348
            } elseif ($scalarLower === ".inf" || $scalarLower === ".nan") {
349
                return -log(0);
350
            } elseif ($scalarLower === "-.inf") {
351
                return log(0);
352
            } elseif (preg_match("/^(-|\\+)?[0-9,]+(\\.[0-9]+)?$/", $scalar)) {
353
                return (float)str_replace(",", "", $scalar);
354
            } elseif (preg_match(self::getTimestampRegex(), $scalar)) {
355
                return strtotime($scalar);
356
            }
357
        } else {
358
            return (string)$scalar;
359
        }
360
    }
361
362
    /**
363
     * Gets a regex that matches a YAML date
364
     *
365
     * @return string The regular expression
366
     *
367
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
368
     */
369
    public static function getTimestampRegex()
370
    {
371
        return <<<EOF
372
        ~^
373
        (?P<year>[0-9][0-9][0-9][0-9])
374
        -(?P<month>[0-9][0-9]?)
375
        -(?P<day>[0-9][0-9]?)
376
        (?:(?:[Tt]|[ \t]+)
377
        (?P<hour>[0-9][0-9]?)
378
        :(?P<minute>[0-9][0-9])
379
        :(?P<second>[0-9][0-9])
380
        (?:\.(?P<fraction>[0-9]*))?
381
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
382
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
383
        $~x
384
EOF;
385
    }
386
387
    /**
388
     * Gets a regex that matches a YAML number in hexadecimal notation.
389
     *
390
     * @return string
391
     */
392
    public static function getHexRegex()
393
    {
394
        return "~^0x[0-9a-f]++$~i";
395
    }
396
}
397