GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( cc39b3...2b121f )
by Anton
04:25 queued 01:05
created

Inline::parseSequence()   D

Complexity

Conditions 22
Paths 21

Size

Total Lines 67
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 22
eloc 39
c 2
b 0
f 0
nc 21
nop 4
dl 0
loc 67
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[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 Symfony\Component\Yaml;
13
14
use Symfony\Component\Yaml\Exception\DumpException;
15
use Symfony\Component\Yaml\Exception\ParseException;
16
use Symfony\Component\Yaml\Tag\TaggedValue;
17
18
/**
19
 * Inline implements a YAML parser/dumper for the YAML inline syntax.
20
 *
21
 * @author Fabien Potencier <[email protected]>
22
 *
23
 * @internal
24
 */
25
class Inline
26
{
27
    public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
28
29
    public static int $parsedLineNumber = -1;
30
    public static ?string $parsedFilename = null;
31
32
    private static bool $exceptionOnInvalidType = false;
33
    private static bool $objectSupport = false;
34
    private static bool $objectForMap = false;
35
    private static bool $constantSupport = false;
36
37
    public static function initialize(int $flags, ?int $parsedLineNumber = null, ?string $parsedFilename = null): void
38
    {
39
        self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
40
        self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
41
        self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
42
        self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
43
        self::$parsedFilename = $parsedFilename;
44
45
        if (null !== $parsedLineNumber) {
46
            self::$parsedLineNumber = $parsedLineNumber;
47
        }
48
    }
49
50
    /**
51
     * Converts a YAML string to a PHP value.
52
     *
53
     * @param int   $flags      A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
54
     * @param array $references Mapping of variable names to values
55
     *
56
     * @throws ParseException
57
     */
58
    public static function parse(string $value, int $flags = 0, array &$references = []): mixed
59
    {
60
        self::initialize($flags);
61
62
        $value = trim($value);
63
64
        if ('' === $value) {
65
            return '';
66
        }
67
68
        $i = 0;
69
        $tag = self::parseTag($value, $i, $flags);
70
        switch ($value[$i]) {
71
            case '[':
72
                $result = self::parseSequence($value, $flags, $i, $references);
73
                ++$i;
74
                break;
75
            case '{':
76
                $result = self::parseMapping($value, $flags, $i, $references);
77
                ++$i;
78
                break;
79
            default:
80
                $result = self::parseScalar($value, $flags, null, $i, true, $references);
81
        }
82
83
        // some comments are allowed at the end
84
        if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
85
            throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
86
        }
87
88
        if (null !== $tag && '' !== $tag) {
89
            return new TaggedValue($tag, $result);
90
        }
91
92
        return $result;
93
    }
94
95
    /**
96
     * Dumps a given PHP variable to a YAML string.
97
     *
98
     * @param mixed $value The PHP variable to convert
99
     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
100
     *
101
     * @throws DumpException When trying to dump PHP resource
102
     */
103
    public static function dump(mixed $value, int $flags = 0): string
104
    {
105
        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...:getHexRegex(), $value) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...-9]+[_0-9]*$}', $value) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...mestampRegex(), $value) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
106
            case \is_resource($value):
107
                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
108
                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
109
                }
110
111
                return self::dumpNull($flags);
112
            case $value instanceof \DateTimeInterface:
113
                return $value->format(match (true) {
114
                    !$length = \strlen(rtrim($value->format('u'), '0')) => 'c',
115
                    $length < 4 => 'Y-m-d\TH:i:s.vP',
116
                    default => 'Y-m-d\TH:i:s.uP',
117
                });
118
            case $value instanceof \UnitEnum:
0 ignored issues
show
Bug introduced by
The type UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
119
                return sprintf('!php/enum %s::%s', $value::class, $value->name);
120
            case \is_object($value):
121
                if ($value instanceof TaggedValue) {
122
                    return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
123
                }
124
125
                if (Yaml::DUMP_OBJECT & $flags) {
126
                    return '!php/object '.self::dump(serialize($value));
127
                }
128
129
                if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
130
                    return self::dumpHashArray($value, $flags);
131
                }
132
133
                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
134
                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
135
                }
136
137
                return self::dumpNull($flags);
138
            case \is_array($value):
139
                return self::dumpArray($value, $flags);
140
            case null === $value:
141
                return self::dumpNull($flags);
142
            case true === $value:
143
                return 'true';
144
            case false === $value:
145
                return 'false';
146
            case \is_int($value):
147
                return $value;
148
            case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"):
149
                $locale = setlocale(\LC_NUMERIC, 0);
150
                if (false !== $locale) {
151
                    setlocale(\LC_NUMERIC, 'C');
152
                }
153
                if (\is_float($value)) {
154
                    $repr = (string) $value;
155
                    if (is_infinite($value)) {
156
                        $repr = str_ireplace('INF', '.Inf', $repr);
157
                    } elseif (floor($value) == $value && $repr == $value) {
158
                        // Preserve float data type since storing a whole number will result in integer value.
159
                        if (!str_contains($repr, 'E')) {
160
                            $repr .= '.0';
161
                        }
162
                    }
163
                } else {
164
                    $repr = \is_string($value) ? "'$value'" : (string) $value;
165
                }
166
                if (false !== $locale) {
167
                    setlocale(\LC_NUMERIC, $locale);
168
                }
169
170
                return $repr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $repr could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
171
            case '' == $value:
172
                return "''";
173
            case self::isBinaryString($value):
174
                return '!!binary '.base64_encode($value);
175
            case Escaper::requiresDoubleQuoting($value):
176
                return Escaper::escapeWithDoubleQuotes($value);
177
            case Escaper::requiresSingleQuoting($value):
178
                $singleQuoted = Escaper::escapeWithSingleQuotes($value);
179
                if (!str_contains($value, "'")) {
180
                    return $singleQuoted;
181
                }
182
                // Attempt double-quoting the string instead to see if it's more efficient.
183
                $doubleQuoted = Escaper::escapeWithDoubleQuotes($value);
184
185
                return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted : $singleQuoted;
186
            case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
187
            case Parser::preg_match(self::getHexRegex(), $value):
188
            case Parser::preg_match(self::getTimestampRegex(), $value):
189
                return Escaper::escapeWithSingleQuotes($value);
190
            default:
191
                return $value;
192
        }
193
    }
194
195
    /**
196
     * Check if given array is hash or just normal indexed array.
197
     */
198
    public static function isHash(array|\ArrayObject|\stdClass $value): bool
199
    {
200
        if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
0 ignored issues
show
introduced by
$value is never a sub-type of ArrayObject.
Loading history...
201
            return true;
202
        }
203
204
        $expectedKey = 0;
205
206
        foreach ($value as $key => $val) {
207
            if ($key !== $expectedKey++) {
208
                return true;
209
            }
210
        }
211
212
        return false;
213
    }
214
215
    /**
216
     * Dumps a PHP array to a YAML string.
217
     *
218
     * @param array $value The PHP array to dump
219
     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
220
     */
221
    private static function dumpArray(array $value, int $flags): string
222
    {
223
        // array
224
        if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $value 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...
225
            $output = [];
226
            foreach ($value as $val) {
227
                $output[] = self::dump($val, $flags);
228
            }
229
230
            return sprintf('[%s]', implode(', ', $output));
231
        }
232
233
        return self::dumpHashArray($value, $flags);
234
    }
235
236
    /**
237
     * Dumps hash array to a YAML string.
238
     *
239
     * @param array|\ArrayObject|\stdClass $value The hash array to dump
240
     * @param int                          $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
241
     */
242
    private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $flags): string
243
    {
244
        $output = [];
245
        foreach ($value as $key => $val) {
246
            if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) {
247
                $key = (string) $key;
248
            }
249
250
            $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
251
        }
252
253
        return sprintf('{ %s }', implode(', ', $output));
254
    }
255
256
    private static function dumpNull(int $flags): string
257
    {
258
        if (Yaml::DUMP_NULL_AS_TILDE & $flags) {
259
            return '~';
260
        }
261
262
        return 'null';
263
    }
264
265
    /**
266
     * Parses a YAML scalar.
267
     *
268
     * @throws ParseException When malformed inline YAML string is parsed
269
     */
270
    public static function parseScalar(string $scalar, int $flags = 0, ?array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], ?bool &$isQuoted = null): mixed
271
    {
272
        if (\in_array($scalar[$i], ['"', "'"], true)) {
273
            // quoted scalar
274
            $isQuoted = true;
275
            $output = self::parseQuotedScalar($scalar, $i);
276
277
            if (null !== $delimiters) {
278
                $tmp = ltrim(substr($scalar, $i), " \n");
279
                if ('' === $tmp) {
280
                    throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
281
                }
282
                if (!\in_array($tmp[0], $delimiters)) {
283
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
284
                }
285
            }
286
        } else {
287
            // "normal" string
288
            $isQuoted = false;
289
290
            if (!$delimiters) {
291
                $output = substr($scalar, $i);
292
                $i += \strlen($output);
293
294
                // remove comments
295
                if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) {
296
                    $output = substr($output, 0, $match[0][1]);
297
                }
298
            } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
299
                $output = $match[1];
300
                $i += \strlen($output);
301
                $output = trim($output);
302
            } else {
303
                throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename);
304
            }
305
306
            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
307
            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) {
308
                throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename);
309
            }
310
311
            if ($evaluate) {
312
                $output = self::evaluateScalar($output, $flags, $references, $isQuoted);
313
            }
314
        }
315
316
        return $output;
317
    }
318
319
    /**
320
     * Parses a YAML quoted scalar.
321
     *
322
     * @throws ParseException When malformed inline YAML string is parsed
323
     */
324
    private static function parseQuotedScalar(string $scalar, int &$i = 0): string
325
    {
326
        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
327
            throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
328
        }
329
330
        $output = substr($match[0], 1, -1);
331
332
        $unescaper = new Unescaper();
333
        if ('"' == $scalar[$i]) {
334
            $output = $unescaper->unescapeDoubleQuotedString($output);
335
        } else {
336
            $output = $unescaper->unescapeSingleQuotedString($output);
337
        }
338
339
        $i += \strlen($match[0]);
340
341
        return $output;
342
    }
343
344
    /**
345
     * Parses a YAML sequence.
346
     *
347
     * @throws ParseException When malformed inline YAML string is parsed
348
     */
349
    private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array
350
    {
351
        $output = [];
352
        $len = \strlen($sequence);
353
        ++$i;
354
355
        // [foo, bar, ...]
356
        $lastToken = null;
357
        while ($i < $len) {
358
            if (']' === $sequence[$i]) {
359
                return $output;
360
            }
361
            if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
362
                if (',' === $sequence[$i] && (null === $lastToken || 'separator' === $lastToken)) {
363
                    $output[] = null;
364
                } elseif (',' === $sequence[$i]) {
365
                    $lastToken = 'separator';
366
                }
367
368
                ++$i;
369
370
                continue;
371
            }
372
373
            $tag = self::parseTag($sequence, $i, $flags);
374
            switch ($sequence[$i]) {
375
                case '[':
376
                    // nested sequence
377
                    $value = self::parseSequence($sequence, $flags, $i, $references);
378
                    break;
379
                case '{':
380
                    // nested mapping
381
                    $value = self::parseMapping($sequence, $flags, $i, $references);
382
                    break;
383
                default:
384
                    $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted);
385
386
                    // the value can be an array if a reference has been resolved to an array var
387
                    if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) {
388
                        // embedded mapping?
389
                        try {
390
                            $pos = 0;
391
                            $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
392
                        } catch (\InvalidArgumentException) {
393
                            // no, it's not
394
                        }
395
                    }
396
397
                    if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
398
                        $references[$matches['ref']] = $matches['value'];
399
                        $value = $matches['value'];
400
                    }
401
402
                    --$i;
403
            }
404
405
            if (null !== $tag && '' !== $tag) {
406
                $value = new TaggedValue($tag, $value);
407
            }
408
409
            $output[] = $value;
410
411
            $lastToken = 'value';
412
            ++$i;
413
        }
414
415
        throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename);
416
    }
417
418
    /**
419
     * Parses a YAML mapping.
420
     *
421
     * @throws ParseException When malformed inline YAML string is parsed
422
     */
423
    private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []): array|\stdClass
424
    {
425
        $output = [];
426
        $len = \strlen($mapping);
427
        ++$i;
428
        $allowOverwrite = false;
429
430
        // {foo: bar, bar:foo, ...}
431
        while ($i < $len) {
432
            switch ($mapping[$i]) {
433
                case ' ':
434
                case ',':
435
                case "\n":
436
                    ++$i;
437
                    continue 2;
438
                case '}':
439
                    if (self::$objectForMap) {
440
                        return (object) $output;
441
                    }
442
443
                    return $output;
444
            }
445
446
            // key
447
            $offsetBeforeKeyParsing = $i;
448
            $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
449
            $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false);
450
451
            if ($offsetBeforeKeyParsing === $i) {
452
                throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
453
            }
454
455
            if ('!php/const' === $key || '!php/enum' === $key) {
456
                $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false);
457
                $key = self::evaluateScalar($key, $flags);
458
            }
459
460
            if (false === $i = strpos($mapping, ':', $i)) {
461
                break;
462
            }
463
464
            if (!$isKeyQuoted) {
465
                $evaluatedKey = self::evaluateScalar($key, $flags, $references);
466
467
                if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
468
                    throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping);
469
                }
470
            }
471
472
            if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) {
473
                throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
474
            }
475
476
            if ('<<' === $key) {
477
                $allowOverwrite = true;
478
            }
479
480
            while ($i < $len) {
481
                if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
482
                    ++$i;
483
484
                    continue;
485
                }
486
487
                $tag = self::parseTag($mapping, $i, $flags);
488
                switch ($mapping[$i]) {
489
                    case '[':
490
                        // nested sequence
491
                        $value = self::parseSequence($mapping, $flags, $i, $references);
492
                        // Spec: Keys MUST be unique; first one wins.
493
                        // Parser cannot abort this mapping earlier, since lines
494
                        // are processed sequentially.
495
                        // But overwriting is allowed when a merge node is used in current block.
496
                        if ('<<' === $key) {
497
                            foreach ($value as $parsedValue) {
498
                                $output += $parsedValue;
499
                            }
500
                        } elseif ($allowOverwrite || !isset($output[$key])) {
501
                            if (null !== $tag) {
502
                                $output[$key] = new TaggedValue($tag, $value);
503
                            } else {
504
                                $output[$key] = $value;
505
                            }
506
                        } elseif (isset($output[$key])) {
507
                            throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
508
                        }
509
                        break;
510
                    case '{':
511
                        // nested mapping
512
                        $value = self::parseMapping($mapping, $flags, $i, $references);
513
                        // Spec: Keys MUST be unique; first one wins.
514
                        // Parser cannot abort this mapping earlier, since lines
515
                        // are processed sequentially.
516
                        // But overwriting is allowed when a merge node is used in current block.
517
                        if ('<<' === $key) {
518
                            $output += $value;
519
                        } elseif ($allowOverwrite || !isset($output[$key])) {
520
                            if (null !== $tag) {
521
                                $output[$key] = new TaggedValue($tag, $value);
522
                            } else {
523
                                $output[$key] = $value;
524
                            }
525
                        } elseif (isset($output[$key])) {
526
                            throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
527
                        }
528
                        break;
529
                    default:
530
                        $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted);
531
                        // Spec: Keys MUST be unique; first one wins.
532
                        // Parser cannot abort this mapping earlier, since lines
533
                        // are processed sequentially.
534
                        // But overwriting is allowed when a merge node is used in current block.
535
                        if ('<<' === $key) {
536
                            $output += $value;
537
                        } elseif ($allowOverwrite || !isset($output[$key])) {
538
                            if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && !self::isBinaryString($value) && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
539
                                $references[$matches['ref']] = $matches['value'];
540
                                $value = $matches['value'];
541
                            }
542
543
                            if (null !== $tag) {
544
                                $output[$key] = new TaggedValue($tag, $value);
545
                            } else {
546
                                $output[$key] = $value;
547
                            }
548
                        } elseif (isset($output[$key])) {
549
                            throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
550
                        }
551
                        --$i;
552
                }
553
                ++$i;
554
555
                continue 2;
556
            }
557
        }
558
559
        throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename);
560
    }
561
562
    /**
563
     * Evaluates scalars and replaces magic values.
564
     *
565
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
566
     */
567
    private static function evaluateScalar(string $scalar, int $flags, array &$references = [], ?bool &$isQuotedString = null): mixed
568
    {
569
        $isQuotedString = false;
570
        $scalar = trim($scalar);
571
572
        if (str_starts_with($scalar, '*')) {
573
            if (false !== $pos = strpos($scalar, '#')) {
574
                $value = substr($scalar, 1, $pos - 2);
575
            } else {
576
                $value = substr($scalar, 1);
577
            }
578
579
            // an unquoted *
580
            if (false === $value || '' === $value) {
581
                throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
582
            }
583
584
            if (!\array_key_exists($value, $references)) {
585
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
586
            }
587
588
            return $references[$value];
589
        }
590
591
        $scalarLower = strtolower($scalar);
592
593
        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/^(?:\+|-)?0...$/', $scalar, $matches) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
594
            case 'null' === $scalarLower:
595
            case '' === $scalar:
596
            case '~' === $scalar:
597
                return null;
598
            case 'true' === $scalarLower:
599
                return true;
600
            case 'false' === $scalarLower:
601
                return false;
602
            case '!' === $scalar[0]:
603
                switch (true) {
604
                    case str_starts_with($scalar, '!!str '):
605
                        $s = (string) substr($scalar, 6);
606
607
                        if (\in_array($s[0] ?? '', ['"', "'"], true)) {
608
                            $isQuotedString = true;
609
                            $s = self::parseQuotedScalar($s);
610
                        }
611
612
                        return $s;
613
                    case str_starts_with($scalar, '! '):
614
                        return substr($scalar, 2);
615
                    case str_starts_with($scalar, '!php/object'):
616
                        if (self::$objectSupport) {
617
                            if (!isset($scalar[12])) {
618
                                throw new ParseException('Missing value for tag "!php/object".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
619
                            }
620
621
                            return unserialize(self::parseScalar(substr($scalar, 12)));
622
                        }
623
624
                        if (self::$exceptionOnInvalidType) {
625
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
626
                        }
627
628
                        return null;
629
                    case str_starts_with($scalar, '!php/const'):
630
                        if (self::$constantSupport) {
631
                            if (!isset($scalar[11])) {
632
                                throw new ParseException('Missing value for tag "!php/const".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
633
                            }
634
635
                            $i = 0;
636
                            if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
637
                                return \constant($const);
638
                            }
639
640
                            throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
641
                        }
642
                        if (self::$exceptionOnInvalidType) {
643
                            throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
644
                        }
645
646
                        return null;
647
                    case str_starts_with($scalar, '!php/enum'):
648
                        if (self::$constantSupport) {
649
                            if (!isset($scalar[11])) {
650
                                throw new ParseException('Missing value for tag "!php/enum".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
651
                            }
652
653
                            $i = 0;
654
                            $enumName = self::parseScalar(substr($scalar, 10), 0, null, $i, false);
655
                            $useName = str_contains($enumName, '::');
656
                            $enum = $useName ? strstr($enumName, '::', true) : $enumName;
657
658
                            if (!enum_exists($enum)) {
659
                                throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
660
                            }
661
                            if (!$useName) {
662
                                return $enum::cases();
663
                            }
664
                            if ($useValue = str_ends_with($enumName, '->value')) {
665
                                $enumName = substr($enumName, 0, -7);
666
                            }
667
668
                            if (!\defined($enumName)) {
669
                                throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enumName), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
670
                            }
671
672
                            $value = \constant($enumName);
673
674
                            if (!$useValue) {
675
                                return $value;
676
                            }
677
                            if (!$value instanceof \BackedEnum) {
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
678
                                throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enumName), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
679
                            }
680
681
                            return $value->value;
682
                        }
683
                        if (self::$exceptionOnInvalidType) {
684
                            throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
685
                        }
686
687
                        return null;
688
                    case str_starts_with($scalar, '!!float '):
689
                        return (float) substr($scalar, 8);
690
                    case str_starts_with($scalar, '!!binary '):
691
                        return self::evaluateBinaryScalar(substr($scalar, 9));
692
                }
693
694
                throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
695
            case preg_match('/^(?:\+|-)?0o(?P<value>[0-7_]++)$/', $scalar, $matches):
696
                $value = str_replace('_', '', $matches['value']);
697
698
                if ('-' === $scalar[0]) {
699
                    return -octdec($value);
700
                }
701
702
                return octdec($value);
703
            case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]):
704
                if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
705
                    $scalar = str_replace('_', '', $scalar);
706
                }
707
708
                switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P....[0-9_]+)?$/', $scalar) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...estampRegex(), $scalar) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...getHexRegex(), $scalar) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
709
                    case ctype_digit($scalar):
710
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
711
                        $cast = (int) $scalar;
712
713
                        return ($scalar === (string) $cast) ? $cast : $scalar;
714
                    case is_numeric($scalar):
715
                    case Parser::preg_match(self::getHexRegex(), $scalar):
716
                        $scalar = str_replace('_', '', $scalar);
717
718
                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
719
                    case '.inf' === $scalarLower:
720
                    case '.nan' === $scalarLower:
721
                        return -log(0);
722
                    case '-.inf' === $scalarLower:
723
                        return log(0);
724
                    case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
725
                        return (float) str_replace('_', '', $scalar);
726
                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
727
                        try {
728
                            // When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
729
                            $time = new \DateTimeImmutable($scalar, new \DateTimeZone('UTC'));
730
                        } catch (\Exception $e) {
731
                            // Some dates accepted by the regex are not valid dates.
732
                            throw new ParseException(\sprintf('The date "%s" could not be parsed as it is an invalid date.', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename, $e);
733
                        }
734
735
                        if (Yaml::PARSE_DATETIME & $flags) {
736
                            return $time;
737
                        }
738
739
                        if ('' !== rtrim($time->format('u'), '0')) {
740
                            return (float) $time->format('U.u');
741
                        }
742
743
                        try {
744
                            if (false !== $scalar = $time->getTimestamp()) {
745
                                return $scalar;
746
                            }
747
                        } catch (\ValueError) {
748
                            // no-op
749
                        }
750
751
                        return $time->format('U');
752
                }
753
        }
754
755
        return (string) $scalar;
756
    }
757
758
    private static function parseTag(string $value, int &$i, int $flags): ?string
759
    {
760
        if ('!' !== $value[$i]) {
761
            return null;
762
        }
763
764
        $tagLength = strcspn($value, " \t\n[]{},", $i + 1);
765
        $tag = substr($value, $i + 1, $tagLength);
766
767
        $nextOffset = $i + $tagLength + 1;
768
        $nextOffset += strspn($value, ' ', $nextOffset);
769
770
        if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) {
771
            throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
772
        }
773
774
        // Is followed by a scalar and is a built-in tag
775
        if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str', 'php/const', 'php/enum', 'php/object'], true))) {
776
            // Manage in {@link self::evaluateScalar()}
777
            return null;
778
        }
779
780
        $i = $nextOffset;
781
782
        // Built-in tags
783
        if ('' !== $tag && '!' === $tag[0]) {
784
            throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
785
        }
786
787
        if ('' !== $tag && !isset($value[$i])) {
788
            throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
789
        }
790
791
        if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) {
792
            return $tag;
793
        }
794
795
        throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
796
    }
797
798
    public static function evaluateBinaryScalar(string $scalar): string
799
    {
800
        $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
801
802
        if (0 !== (\strlen($parsedBinaryData) % 4)) {
803
            throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
804
        }
805
806
        if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
807
            throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
808
        }
809
810
        return base64_decode($parsedBinaryData, true);
811
    }
812
813
    private static function isBinaryString(string $value): bool
814
    {
815
        return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
816
    }
817
818
    /**
819
     * Gets a regex that matches a YAML date.
820
     *
821
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
822
     */
823
    private static function getTimestampRegex(): string
824
    {
825
        return <<<EOF
826
        ~^
827
        (?P<year>[0-9][0-9][0-9][0-9])
828
        -(?P<month>[0-9][0-9]?)
829
        -(?P<day>[0-9][0-9]?)
830
        (?:(?:[Tt]|[ \t]+)
831
        (?P<hour>[0-9][0-9]?)
832
        :(?P<minute>[0-9][0-9])
833
        :(?P<second>[0-9][0-9])
834
        (?:\.(?P<fraction>[0-9]*))?
835
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
836
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
837
        $~x
838
EOF;
839
    }
840
841
    /**
842
     * Gets a regex that matches a YAML number in hexadecimal notation.
843
     */
844
    private static function getHexRegex(): string
845
    {
846
        return '~^0x[0-9a-f_]++$~i';
847
    }
848
}
849