Inline::isBinaryString()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
28
29
    public static $parsedLineNumber = -1;
30
    public static $parsedFilename;
31
32
    private static $exceptionOnInvalidType = false;
33
    private static $objectSupport = false;
34
    private static $objectForMap = false;
35
    private static $constantSupport = false;
36
37
    /**
38
     * @param int         $flags
39
     * @param int|null    $parsedLineNumber
40
     * @param string|null $parsedFilename
41
     */
42
    public static function initialize($flags, $parsedLineNumber = null, $parsedFilename = null)
43
    {
44
        self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
45
        self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
46
        self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
47
        self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
48
        self::$parsedFilename = $parsedFilename;
49
50
        if (null !== $parsedLineNumber) {
51
            self::$parsedLineNumber = $parsedLineNumber;
52
        }
53
    }
54
55
    /**
56
     * Converts a YAML string to a PHP value.
57
     *
58
     * @param string $value      A YAML string
59
     * @param int    $flags      A bit field of PARSE_* constants to customize the YAML parser behavior
60
     * @param array  $references Mapping of variable names to values
61
     *
62
     * @return mixed A PHP value
63
     *
64
     * @throws ParseException
65
     */
66
    public static function parse($value, $flags = 0, $references = [])
67
    {
68
        if (\is_bool($flags)) {
69
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED);
70
71
            if ($flags) {
72
                $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
73
            } else {
74
                $flags = 0;
75
            }
76
        }
77
78
        if (\func_num_args() >= 3 && !\is_array($references)) {
79
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', \E_USER_DEPRECATED);
80
81
            if ($references) {
82
                $flags |= Yaml::PARSE_OBJECT;
83
            }
84
85
            if (\func_num_args() >= 4) {
86
                @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', \E_USER_DEPRECATED);
87
88
                if (func_get_arg(3)) {
89
                    $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
90
                }
91
            }
92
93
            if (\func_num_args() >= 5) {
94
                $references = func_get_arg(4);
95
            } else {
96
                $references = [];
97
            }
98
        }
99
100
        self::initialize($flags);
101
102
        $value = trim($value);
103
104
        if ('' === $value) {
105
            return '';
106
        }
107
108
        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
109
            $mbEncoding = mb_internal_encoding();
110
            mb_internal_encoding('ASCII');
111
        }
112
113
        try {
114
            $i = 0;
115
            $tag = self::parseTag($value, $i, $flags);
116
            switch ($value[$i]) {
117
                case '[':
118
                    $result = self::parseSequence($value, $flags, $i, $references);
119
                    ++$i;
120
                    break;
121
                case '{':
122
                    $result = self::parseMapping($value, $flags, $i, $references);
123
                    ++$i;
124
                    break;
125
                default:
126
                    $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
127
            }
128
129
            // some comments are allowed at the end
130
            if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
131
                throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
132
            }
133
134
            if (null !== $tag) {
135
                return new TaggedValue($tag, $result);
136
            }
137
138
            return $result;
139
        } finally {
140
            if (isset($mbEncoding)) {
141
                mb_internal_encoding($mbEncoding);
142
            }
143
        }
144
    }
145
146
    /**
147
     * Dumps a given PHP variable to a YAML string.
148
     *
149
     * @param mixed $value The PHP variable to convert
150
     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
151
     *
152
     * @return string The YAML string representing the PHP value
153
     *
154
     * @throws DumpException When trying to dump PHP resource
155
     */
156
    public static function dump($value, $flags = 0)
157
    {
158
        if (\is_bool($flags)) {
159
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED);
160
161
            if ($flags) {
162
                $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE;
163
            } else {
164
                $flags = 0;
165
            }
166
        }
167
168
        if (\func_num_args() >= 3) {
169
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', \E_USER_DEPRECATED);
170
171
            if (func_get_arg(2)) {
172
                $flags |= Yaml::DUMP_OBJECT;
173
            }
174
        }
175
176
        switch (true) {
0 ignored issues
show
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...
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...: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...
177
            case \is_resource($value):
178
                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
179
                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
180
                }
181
182
                return 'null';
183
            case $value instanceof \DateTimeInterface:
184
                return $value->format('c');
185
            case \is_object($value):
186
                if ($value instanceof TaggedValue) {
187
                    return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
188
                }
189
190
                if (Yaml::DUMP_OBJECT & $flags) {
191
                    return '!php/object '.self::dump(serialize($value));
192
                }
193
194
                if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
195
                    return self::dumpArray($value, $flags & ~Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
0 ignored issues
show
Bug introduced by
$value of type ArrayObject|stdClass is incompatible with the type array expected by parameter $value of Symfony\Component\Yaml\Inline::dumpArray(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

195
                    return self::dumpArray(/** @scrutinizer ignore-type */ $value, $flags & ~Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
Loading history...
196
                }
197
198
                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
199
                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
200
                }
201
202
                return 'null';
203
            case \is_array($value):
204
                return self::dumpArray($value, $flags);
205
            case null === $value:
206
                return 'null';
207
            case true === $value:
208
                return 'true';
209
            case false === $value:
210
                return 'false';
211
            case ctype_digit($value):
212
                return \is_string($value) ? "'$value'" : (int) $value;
213
            case is_numeric($value) && false === strpos($value, "\f") && false === strpos($value, "\n") && false === strpos($value, "\r") && false === strpos($value, "\t") && false === strpos($value, "\v"):
214
                $locale = setlocale(\LC_NUMERIC, 0);
215
                if (false !== $locale) {
216
                    setlocale(\LC_NUMERIC, 'C');
217
                }
218
                if (\is_float($value)) {
219
                    $repr = (string) $value;
220
                    if (is_infinite($value)) {
221
                        $repr = str_ireplace('INF', '.Inf', $repr);
222
                    } elseif (floor($value) == $value && $repr == $value) {
223
                        // Preserve float data type since storing a whole number will result in integer value.
224
                        $repr = '!!float '.$repr;
225
                    }
226
                } else {
227
                    $repr = \is_string($value) ? "'$value'" : (string) $value;
228
                }
229
                if (false !== $locale) {
230
                    setlocale(\LC_NUMERIC, $locale);
231
                }
232
233
                return $repr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $repr also could return the type array which is incompatible with the documented return type string.
Loading history...
234
            case '' == $value:
235
                return "''";
236
            case self::isBinaryString($value):
237
                return '!!binary '.base64_encode($value);
238
            case Escaper::requiresDoubleQuoting($value):
239
                return Escaper::escapeWithDoubleQuotes($value);
240
            case Escaper::requiresSingleQuoting($value):
241
            case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
242
            case Parser::preg_match(self::getHexRegex(), $value):
243
            case Parser::preg_match(self::getTimestampRegex(), $value):
244
                return Escaper::escapeWithSingleQuotes($value);
245
            default:
246
                return $value;
247
        }
248
    }
249
250
    /**
251
     * Check if given array is hash or just normal indexed array.
252
     *
253
     * @internal
254
     *
255
     * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
256
     *
257
     * @return bool true if value is hash array, false otherwise
258
     */
259
    public static function isHash($value)
260
    {
261
        if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
262
            return true;
263
        }
264
265
        $expectedKey = 0;
266
267
        foreach ($value as $key => $val) {
268
            if ($key !== $expectedKey++) {
269
                return true;
270
            }
271
        }
272
273
        return false;
274
    }
275
276
    /**
277
     * Dumps a PHP array to a YAML string.
278
     *
279
     * @param array $value The PHP array to dump
280
     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
281
     *
282
     * @return string The YAML string representing the PHP array
283
     */
284
    private static function dumpArray($value, $flags)
285
    {
286
        // array
287
        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...
288
            $output = [];
289
            foreach ($value as $val) {
290
                $output[] = self::dump($val, $flags);
291
            }
292
293
            return sprintf('[%s]', implode(', ', $output));
294
        }
295
296
        // hash
297
        $output = [];
298
        foreach ($value as $key => $val) {
299
            $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
300
        }
301
302
        return sprintf('{ %s }', implode(', ', $output));
303
    }
304
305
    /**
306
     * Parses a YAML scalar.
307
     *
308
     * @param string   $scalar
309
     * @param int      $flags
310
     * @param string[] $delimiters
311
     * @param int      &$i
312
     * @param bool     $evaluate
313
     * @param array    $references
314
     *
315
     * @return string
316
     *
317
     * @throws ParseException When malformed inline YAML string is parsed
318
     *
319
     * @internal
320
     */
321
    public static function parseScalar($scalar, $flags = 0, $delimiters = null, &$i = 0, $evaluate = true, $references = [], $legacyOmittedKeySupport = false)
322
    {
323
        if (\in_array($scalar[$i], ['"', "'"])) {
324
            // quoted scalar
325
            $output = self::parseQuotedScalar($scalar, $i);
326
327
            if (null !== $delimiters) {
328
                $tmp = ltrim(substr($scalar, $i), ' ');
329
                if ('' === $tmp) {
330
                    throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
331
                }
332
                if (!\in_array($tmp[0], $delimiters)) {
333
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
334
                }
335
            }
336
        } else {
337
            // "normal" string
338
            if (!$delimiters) {
339
                $output = substr($scalar, $i);
340
                $i += \strlen($output);
341
342
                // remove comments
343
                if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) {
344
                    $output = substr($output, 0, $match[0][1]);
345
                }
346
            } elseif (Parser::preg_match('/^(.'.($legacyOmittedKeySupport ? '+' : '*').'?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
347
                $output = $match[1];
348
                $i += \strlen($output);
349
            } else {
350
                throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename);
351
            }
352
353
            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
354
            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
355
                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);
356
            }
357
358
            if ($output && '%' === $output[0]) {
359
                @trigger_error(self::getDeprecationMessage(sprintf('Not quoting the scalar "%s" starting with the "%%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0.', $output)), \E_USER_DEPRECATED);
360
            }
361
362
            if ($evaluate) {
363
                $output = self::evaluateScalar($output, $flags, $references);
364
            }
365
        }
366
367
        return $output;
368
    }
369
370
    /**
371
     * Parses a YAML quoted scalar.
372
     *
373
     * @param string $scalar
374
     * @param int    &$i
375
     *
376
     * @return string
377
     *
378
     * @throws ParseException When malformed inline YAML string is parsed
379
     */
380
    private static function parseQuotedScalar($scalar, &$i)
381
    {
382
        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
383
            throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
384
        }
385
386
        $output = substr($match[0], 1, \strlen($match[0]) - 2);
387
388
        $unescaper = new Unescaper();
389
        if ('"' == $scalar[$i]) {
390
            $output = $unescaper->unescapeDoubleQuotedString($output);
391
        } else {
392
            $output = $unescaper->unescapeSingleQuotedString($output);
393
        }
394
395
        $i += \strlen($match[0]);
396
397
        return $output;
398
    }
399
400
    /**
401
     * Parses a YAML sequence.
402
     *
403
     * @param string $sequence
404
     * @param int    $flags
405
     * @param int    &$i
406
     * @param array  $references
407
     *
408
     * @return array
409
     *
410
     * @throws ParseException When malformed inline YAML string is parsed
411
     */
412
    private static function parseSequence($sequence, $flags, &$i = 0, $references = [])
413
    {
414
        $output = [];
415
        $len = \strlen($sequence);
416
        ++$i;
417
418
        // [foo, bar, ...]
419
        while ($i < $len) {
420
            if (']' === $sequence[$i]) {
421
                return $output;
422
            }
423
            if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
424
                ++$i;
425
426
                continue;
427
            }
428
429
            $tag = self::parseTag($sequence, $i, $flags);
430
            switch ($sequence[$i]) {
431
                case '[':
432
                    // nested sequence
433
                    $value = self::parseSequence($sequence, $flags, $i, $references);
434
                    break;
435
                case '{':
436
                    // nested mapping
437
                    $value = self::parseMapping($sequence, $flags, $i, $references);
438
                    break;
439
                default:
440
                    $isQuoted = \in_array($sequence[$i], ['"', "'"]);
441
                    $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references);
442
443
                    // the value can be an array if a reference has been resolved to an array var
444
                    if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
445
                        // embedded mapping?
446
                        try {
447
                            $pos = 0;
448
                            $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
449
                        } catch (\InvalidArgumentException $e) {
450
                            // no, it's not
451
                        }
452
                    }
453
454
                    --$i;
455
            }
456
457
            if (null !== $tag) {
458
                $value = new TaggedValue($tag, $value);
459
            }
460
461
            $output[] = $value;
462
463
            ++$i;
464
        }
465
466
        throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename);
467
    }
468
469
    /**
470
     * Parses a YAML mapping.
471
     *
472
     * @param string $mapping
473
     * @param int    $flags
474
     * @param int    &$i
475
     * @param array  $references
476
     *
477
     * @return array|\stdClass
478
     *
479
     * @throws ParseException When malformed inline YAML string is parsed
480
     */
481
    private static function parseMapping($mapping, $flags, &$i = 0, $references = [])
482
    {
483
        $output = [];
484
        $len = \strlen($mapping);
485
        ++$i;
486
        $allowOverwrite = false;
487
488
        // {foo: bar, bar:foo, ...}
489
        while ($i < $len) {
490
            switch ($mapping[$i]) {
491
                case ' ':
492
                case ',':
493
                    ++$i;
494
                    continue 2;
495
                case '}':
496
                    if (self::$objectForMap) {
497
                        return (object) $output;
498
                    }
499
500
                    return $output;
501
            }
502
503
            // key
504
            $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
505
            $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, [], true);
506
507
            if ('!php/const' === $key) {
508
                $key .= self::parseScalar($mapping, $flags, [':', ' '], $i, false, [], true);
509
                if ('!php/const:' === $key && ':' !== $mapping[$i]) {
510
                    $key = '';
511
                    --$i;
512
                } else {
513
                    $key = self::evaluateScalar($key, $flags);
514
                }
515
            }
516
517
            if (':' !== $key && false === $i = strpos($mapping, ':', $i)) {
518
                break;
519
            }
520
521
            if (':' === $key) {
522
                @trigger_error(self::getDeprecationMessage('Omitting the key of a mapping is deprecated and will throw a ParseException in 4.0.'), \E_USER_DEPRECATED);
523
            }
524
525
            if (!$isKeyQuoted) {
526
                $evaluatedKey = self::evaluateScalar($key, $flags, $references);
527
528
                if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
529
                    @trigger_error(self::getDeprecationMessage('Implicit casting of incompatible mapping keys to strings is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.'), \E_USER_DEPRECATED);
530
                }
531
            }
532
533
            if (':' !== $key && !$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}'], true))) {
534
                @trigger_error(self::getDeprecationMessage('Using a colon after an unquoted mapping key that is not followed by an indication character (i.e. " ", ",", "[", "]", "{", "}") is deprecated since Symfony 3.2 and will throw a ParseException in 4.0.'), \E_USER_DEPRECATED);
535
            }
536
537
            if ('<<' === $key) {
538
                $allowOverwrite = true;
539
            }
540
541
            while ($i < $len) {
542
                if (':' === $mapping[$i] || ' ' === $mapping[$i]) {
543
                    ++$i;
544
545
                    continue;
546
                }
547
548
                $tag = self::parseTag($mapping, $i, $flags);
549
                switch ($mapping[$i]) {
550
                    case '[':
551
                        // nested sequence
552
                        $value = self::parseSequence($mapping, $flags, $i, $references);
553
                        // Spec: Keys MUST be unique; first one wins.
554
                        // Parser cannot abort this mapping earlier, since lines
555
                        // are processed sequentially.
556
                        // But overwriting is allowed when a merge node is used in current block.
557
                        if ('<<' === $key) {
558
                            foreach ($value as $parsedValue) {
559
                                $output += $parsedValue;
560
                            }
561
                        } elseif ($allowOverwrite || !isset($output[$key])) {
562
                            if (null !== $tag) {
563
                                $output[$key] = new TaggedValue($tag, $value);
564
                            } else {
565
                                $output[$key] = $value;
566
                            }
567
                        } elseif (isset($output[$key])) {
568
                            @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED);
569
                        }
570
                        break;
571
                    case '{':
572
                        // nested mapping
573
                        $value = self::parseMapping($mapping, $flags, $i, $references);
574
                        // Spec: Keys MUST be unique; first one wins.
575
                        // Parser cannot abort this mapping earlier, since lines
576
                        // are processed sequentially.
577
                        // But overwriting is allowed when a merge node is used in current block.
578
                        if ('<<' === $key) {
579
                            $output += $value;
580
                        } elseif ($allowOverwrite || !isset($output[$key])) {
581
                            if (null !== $tag) {
582
                                $output[$key] = new TaggedValue($tag, $value);
583
                            } else {
584
                                $output[$key] = $value;
585
                            }
586
                        } elseif (isset($output[$key])) {
587
                            @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED);
588
                        }
589
                        break;
590
                    default:
591
                        $value = self::parseScalar($mapping, $flags, [',', '}'], $i, null === $tag, $references);
592
                        // Spec: Keys MUST be unique; first one wins.
593
                        // Parser cannot abort this mapping earlier, since lines
594
                        // are processed sequentially.
595
                        // But overwriting is allowed when a merge node is used in current block.
596
                        if ('<<' === $key) {
597
                            $output += $value;
598
                        } elseif ($allowOverwrite || !isset($output[$key])) {
599
                            if (null !== $tag) {
600
                                $output[$key] = new TaggedValue($tag, $value);
601
                            } else {
602
                                $output[$key] = $value;
603
                            }
604
                        } elseif (isset($output[$key])) {
605
                            @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED);
606
                        }
607
                        --$i;
608
                }
609
                ++$i;
610
611
                continue 2;
612
            }
613
        }
614
615
        throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename);
616
    }
617
618
    /**
619
     * Evaluates scalars and replaces magic values.
620
     *
621
     * @param string $scalar
622
     * @param int    $flags
623
     * @param array  $references
624
     *
625
     * @return mixed The evaluated YAML string
626
     *
627
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
628
     */
629
    private static function evaluateScalar($scalar, $flags, $references = [])
630
    {
631
        $scalar = trim($scalar);
632
        $scalarLower = strtolower($scalar);
633
634
        if (0 === strpos($scalar, '*')) {
635
            if (false !== $pos = strpos($scalar, '#')) {
636
                $value = substr($scalar, 1, $pos - 2);
637
            } else {
638
                $value = substr($scalar, 1);
639
            }
640
641
            // an unquoted *
642
            if (false === $value || '' === $value) {
643
                throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
644
            }
645
646
            if (!\array_key_exists($value, $references)) {
647
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
648
            }
649
650
            return $references[$value];
651
        }
652
653
        switch (true) {
654
            case 'null' === $scalarLower:
655
            case '' === $scalar:
656
            case '~' === $scalar:
657
                return null;
658
            case 'true' === $scalarLower:
659
                return true;
660
            case 'false' === $scalarLower:
661
                return false;
662
            case '!' === $scalar[0]:
663
                switch (true) {
664
                    case 0 === strpos($scalar, '!str'):
665
                        @trigger_error(self::getDeprecationMessage('Support for the !str tag is deprecated since Symfony 3.4. Use the !!str tag instead.'), \E_USER_DEPRECATED);
666
667
                        return (string) substr($scalar, 5);
668
                    case 0 === strpos($scalar, '!!str '):
669
                        return (string) substr($scalar, 6);
670
                    case 0 === strpos($scalar, '! '):
671
                        @trigger_error(self::getDeprecationMessage('Using the non-specific tag "!" is deprecated since Symfony 3.4 as its behavior will change in 4.0. It will force non-evaluating your values in 4.0. Use plain integers or !!float instead.'), \E_USER_DEPRECATED);
672
673
                        return (int) self::parseScalar(substr($scalar, 2), $flags);
674
                    case 0 === strpos($scalar, '!php/object:'):
675
                        if (self::$objectSupport) {
676
                            @trigger_error(self::getDeprecationMessage('The !php/object: tag to indicate dumped PHP objects is deprecated since Symfony 3.4 and will be removed in 4.0. Use the !php/object (without the colon) tag instead.'), \E_USER_DEPRECATED);
677
678
                            return unserialize(substr($scalar, 12));
679
                        }
680
681
                        if (self::$exceptionOnInvalidType) {
682
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
683
                        }
684
685
                        return null;
686
                    case 0 === strpos($scalar, '!!php/object:'):
687
                        if (self::$objectSupport) {
688
                            @trigger_error(self::getDeprecationMessage('The !!php/object: tag to indicate dumped PHP objects is deprecated since Symfony 3.1 and will be removed in 4.0. Use the !php/object (without the colon) tag instead.'), \E_USER_DEPRECATED);
689
690
                            return unserialize(substr($scalar, 13));
691
                        }
692
693
                        if (self::$exceptionOnInvalidType) {
694
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
695
                        }
696
697
                        return null;
698
                    case 0 === strpos($scalar, '!php/object'):
699
                        if (self::$objectSupport) {
700
                            if (!isset($scalar[12])) {
701
                                return false;
702
                            }
703
704
                            return unserialize(self::parseScalar(substr($scalar, 12)));
705
                        }
706
707
                        if (self::$exceptionOnInvalidType) {
708
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
709
                        }
710
711
                        return null;
712
                    case 0 === strpos($scalar, '!php/const:'):
713
                        if (self::$constantSupport) {
714
                            @trigger_error(self::getDeprecationMessage('The !php/const: tag to indicate dumped PHP constants is deprecated since Symfony 3.4 and will be removed in 4.0. Use the !php/const (without the colon) tag instead.'), \E_USER_DEPRECATED);
715
716
                            if (\defined($const = substr($scalar, 11))) {
717
                                return \constant($const);
718
                            }
719
720
                            throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
721
                        }
722
                        if (self::$exceptionOnInvalidType) {
723
                            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);
724
                        }
725
726
                        return null;
727
                    case 0 === strpos($scalar, '!php/const'):
728
                        if (self::$constantSupport) {
729
                            if (!isset($scalar[11])) {
730
                                return '';
731
                            }
732
733
                            $i = 0;
734
                            if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
735
                                return \constant($const);
736
                            }
737
738
                            throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
739
                        }
740
                        if (self::$exceptionOnInvalidType) {
741
                            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);
742
                        }
743
744
                        return null;
745
                    case 0 === strpos($scalar, '!!float '):
746
                        return (float) substr($scalar, 8);
747
                    case 0 === strpos($scalar, '!!binary '):
748
                        return self::evaluateBinaryScalar(substr($scalar, 9));
749
                    default:
750
                        @trigger_error(self::getDeprecationMessage(sprintf('Using the unquoted scalar value "%s" is deprecated since Symfony 3.3 and will be considered as a tagged value in 4.0. You must quote it.', $scalar)), \E_USER_DEPRECATED);
751
                }
752
753
            // Optimize for returning strings.
754
            // no break
755
            case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]):
756
                if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
757
                    $scalar = str_replace('_', '', (string) $scalar);
758
                }
759
760
                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...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...
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....[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...
761
                    case ctype_digit($scalar):
762
                        if (preg_match('/^0[0-7]+$/', $scalar)) {
763
                            return octdec($scalar);
764
                        }
765
766
                        $cast = (int) $scalar;
767
768
                        return ($scalar === (string) $cast) ? $cast : $scalar;
769
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
770
                        if (preg_match('/^-0[0-7]+$/', $scalar)) {
771
                            return -octdec(substr($scalar, 1));
772
                        }
773
774
                        $cast = (int) $scalar;
775
776
                        return ($scalar === (string) $cast) ? $cast : $scalar;
777
                    case is_numeric($scalar):
778
                    case Parser::preg_match(self::getHexRegex(), $scalar):
779
                        $scalar = str_replace('_', '', $scalar);
780
781
                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
782
                    case '.inf' === $scalarLower:
783
                    case '.nan' === $scalarLower:
784
                        return -log(0);
785
                    case '-.inf' === $scalarLower:
786
                        return log(0);
787
                    case Parser::preg_match('/^(-|\+)?[0-9][0-9,]*(\.[0-9_]+)?$/', $scalar):
788
                    case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
789
                        if (false !== strpos($scalar, ',')) {
790
                            @trigger_error(self::getDeprecationMessage('Using the comma as a group separator for floats is deprecated since Symfony 3.2 and will be removed in 4.0.'), \E_USER_DEPRECATED);
791
                        }
792
793
                        return (float) str_replace([',', '_'], '', $scalar);
794
                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
795
                        if (Yaml::PARSE_DATETIME & $flags) {
796
                            // When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
797
                            return new \DateTime($scalar, new \DateTimeZone('UTC'));
798
                        }
799
800
                        $timeZone = date_default_timezone_get();
801
                        date_default_timezone_set('UTC');
802
                        $time = strtotime($scalar);
803
                        date_default_timezone_set($timeZone);
804
805
                        return $time;
806
                }
807
        }
808
809
        return (string) $scalar;
810
    }
811
812
    /**
813
     * @param string $value
814
     * @param int    &$i
815
     * @param int    $flags
816
     *
817
     * @return string|null
818
     */
819
    private static function parseTag($value, &$i, $flags)
820
    {
821
        if ('!' !== $value[$i]) {
822
            return null;
823
        }
824
825
        $tagLength = strcspn($value, " \t\n", $i + 1);
826
        $tag = substr($value, $i + 1, $tagLength);
827
828
        $nextOffset = $i + $tagLength + 1;
829
        $nextOffset += strspn($value, ' ', $nextOffset);
830
831
        // Is followed by a scalar
832
        if ((!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && 'tagged' !== $tag) {
833
            // Manage non-whitelisted scalars in {@link self::evaluateScalar()}
834
            return null;
835
        }
836
837
        // Built-in tags
838
        if ($tag && '!' === $tag[0]) {
839
            throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
840
        }
841
842
        if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
843
            $i = $nextOffset;
844
845
            return $tag;
846
        }
847
848
        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);
849
    }
850
851
    /**
852
     * @param string $scalar
853
     *
854
     * @return string
855
     *
856
     * @internal
857
     */
858
    public static function evaluateBinaryScalar($scalar)
859
    {
860
        $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
861
862
        if (0 !== (\strlen($parsedBinaryData) % 4)) {
863
            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);
864
        }
865
866
        if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
867
            throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
868
        }
869
870
        return base64_decode($parsedBinaryData, true);
871
    }
872
873
    private static function isBinaryString($value)
874
    {
875
        return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
876
    }
877
878
    /**
879
     * Gets a regex that matches a YAML date.
880
     *
881
     * @return string The regular expression
882
     *
883
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
884
     */
885
    private static function getTimestampRegex()
886
    {
887
        return <<<EOF
888
        ~^
889
        (?P<year>[0-9][0-9][0-9][0-9])
890
        -(?P<month>[0-9][0-9]?)
891
        -(?P<day>[0-9][0-9]?)
892
        (?:(?:[Tt]|[ \t]+)
893
        (?P<hour>[0-9][0-9]?)
894
        :(?P<minute>[0-9][0-9])
895
        :(?P<second>[0-9][0-9])
896
        (?:\.(?P<fraction>[0-9]*))?
897
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
898
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
899
        $~x
900
EOF;
901
    }
902
903
    /**
904
     * Gets a regex that matches a YAML number in hexadecimal notation.
905
     *
906
     * @return string
907
     */
908
    private static function getHexRegex()
909
    {
910
        return '~^0x[0-9a-f_]++$~i';
911
    }
912
913
    private static function getDeprecationMessage($message)
914
    {
915
        $message = rtrim($message, '.');
916
917
        if (null !== self::$parsedFilename) {
918
            $message .= ' in '.self::$parsedFilename;
919
        }
920
921
        if (-1 !== self::$parsedLineNumber) {
922
            $message .= ' on line '.(self::$parsedLineNumber + 1);
923
        }
924
925
        return $message.'.';
926
    }
927
}
928