1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Yaml; 13 14use Symfony\Component\Yaml\Exception\DumpException; 15use Symfony\Component\Yaml\Exception\ParseException; 16use Symfony\Component\Yaml\Tag\TaggedValue; 17 18/** 19 * Inline implements a YAML parser/dumper for the YAML inline syntax. 20 * 21 * @author Fabien Potencier <fabien@symfony.com> 22 * 23 * @internal 24 */ 25class 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) { 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); 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): 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; 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)) { 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) { 761 case ctype_digit($scalar): 762 if ('0' === $scalar[0]) { 763 return octdec(preg_replace('/[^0-7]/', '', $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 ('0' === $scalar[1]) { 771 return -octdec(preg_replace('/[^0-7]/', '', 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 900EOF; 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