X-Git-Url: https://yaffs.net/gitweb/?a=blobdiff_plain;f=vendor%2Fsymfony%2Fyaml%2FInline.php;h=7c9d4e03b5fd3538734dd9e727eca6205c600094;hb=1c1cb0980bfa6caf0c24cce671b6bb541dc87583;hp=a2674369c5cad72d103f4c2230c0f34c265c464d;hpb=a2bd1bf0c2c1f1a17d188f4dc0726a45494cefae;p=yaffs-website diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php index a2674369c..7c9d4e03b 100644 --- a/vendor/symfony/yaml/Inline.php +++ b/vendor/symfony/yaml/Inline.php @@ -11,40 +11,93 @@ namespace Symfony\Component\Yaml; -use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; /** * Inline implements a YAML parser/dumper for the YAML inline syntax. * * @author Fabien Potencier + * + * @internal */ class Inline { const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + public static $parsedLineNumber = -1; + public static $parsedFilename; + private static $exceptionOnInvalidType = false; private static $objectSupport = false; private static $objectForMap = false; + private static $constantSupport = false; + + /** + * @param int $flags + * @param int|null $parsedLineNumber + * @param string|null $parsedFilename + */ + public static function initialize($flags, $parsedLineNumber = null, $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } /** * Converts a YAML string to a PHP value. * - * @param string $value A YAML string - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise - * @param bool $objectForMap true if maps should return a stdClass instead of array() - * @param array $references Mapping of variable names to values + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values * * @return mixed A PHP value * * @throws ParseException */ - public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) + public static function parse($value, $flags = 0, $references = array()) { - self::$exceptionOnInvalidType = $exceptionOnInvalidType; - self::$objectSupport = $objectSupport; - self::$objectForMap = $objectForMap; + if (\is_bool($flags)) { + @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); + + if ($flags) { + $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; + } else { + $flags = 0; + } + } + + if (\func_num_args() >= 3 && !\is_array($references)) { + @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); + + if ($references) { + $flags |= Yaml::PARSE_OBJECT; + } + + if (\func_num_args() >= 4) { + @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); + + if (func_get_arg(3)) { + $flags |= Yaml::PARSE_OBJECT_FOR_MAP; + } + } + + if (\func_num_args() >= 5) { + $references = func_get_arg(4); + } else { + $references = array(); + } + } + + self::initialize($flags); $value = trim($value); @@ -58,22 +111,27 @@ class Inline } $i = 0; - switch ($value[0]) { + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { case '[': - $result = self::parseSequence($value, $i, $references); + $result = self::parseSequence($value, $flags, $i, $references); ++$i; break; case '{': - $result = self::parseMapping($value, $i, $references); + $result = self::parseMapping($value, $flags, $i, $references); ++$i; break; default: - $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); + $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references); + } + + if (null !== $tag) { + return new TaggedValue($tag, $result); } // some comments are allowed at the end if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { - throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if (isset($mbEncoding)) { @@ -86,35 +144,62 @@ class Inline /** * Dumps a given PHP variable to a YAML string. * - * @param mixed $value The PHP variable to convert - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string * * @return string The YAML string representing the PHP value * * @throws DumpException When trying to dump PHP resource */ - public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) + public static function dump($value, $flags = 0) { + if (\is_bool($flags)) { + @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); + + if ($flags) { + $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE; + } else { + $flags = 0; + } + } + + if (\func_num_args() >= 3) { + @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); + + if (func_get_arg(2)) { + $flags |= Yaml::DUMP_OBJECT; + } + } + switch (true) { - case is_resource($value): - if ($exceptionOnInvalidType) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; - case is_object($value): - if ($objectSupport) { - return '!php/object:'.serialize($value); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + return self::dumpArray($value, $flags & ~Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); } - if ($exceptionOnInvalidType) { + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; - case is_array($value): - return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); + case \is_array($value): + return self::dumpArray($value, $flags); case null === $value: return 'null'; case true === $value: @@ -122,13 +207,13 @@ class Inline case false === $value: return 'false'; case ctype_digit($value): - return is_string($value) ? "'$value'" : (int) $value; + return \is_string($value) ? "'$value'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } - if (is_float($value)) { + if (\is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); @@ -137,7 +222,7 @@ class Inline $repr = '!!float '.$repr; } } else { - $repr = is_string($value) ? "'$value'" : (string) $value; + $repr = \is_string($value) ? "'$value'" : (string) $value; } if (false !== $locale) { setlocale(LC_NUMERIC, $locale); @@ -146,9 +231,12 @@ class Inline return $repr; case '' == $value: return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): case Parser::preg_match(self::getHexRegex(), $value): case Parser::preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); @@ -162,12 +250,16 @@ class Inline * * @internal * - * @param array $value The PHP array to check + * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check * * @return bool true if value is hash array, false otherwise */ - public static function isHash(array $value) + public static function isHash($value) { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + $expectedKey = 0; foreach ($value as $key => $val) { @@ -182,19 +274,18 @@ class Inline /** * Dumps a PHP array to a YAML string. * - * @param array $value The PHP array to dump - * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise - * @param bool $objectSupport true if object support is enabled, false otherwise + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string * * @return string The YAML string representing the PHP array */ - private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) + private static function dumpArray($value, $flags) { // array - if ($value && !self::isHash($value)) { + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { $output = array(); foreach ($value as $val) { - $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); + $output[] = self::dump($val, $flags); } return sprintf('[%s]', implode(', ', $output)); @@ -203,7 +294,7 @@ class Inline // hash $output = array(); foreach ($value as $key => $val) { - $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } return sprintf('{ %s }', implode(', ', $output)); @@ -212,12 +303,12 @@ class Inline /** * Parses a YAML scalar. * - * @param string $scalar - * @param string $delimiters - * @param array $stringDelimiters - * @param int &$i - * @param bool $evaluate - * @param array $references + * @param string $scalar + * @param int $flags + * @param string[] $delimiters + * @param int &$i + * @param bool $evaluate + * @param array $references * * @return string * @@ -225,45 +316,49 @@ class Inline * * @internal */ - public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) + public static function parseScalar($scalar, $flags = 0, $delimiters = null, &$i = 0, $evaluate = true, $references = array(), $legacyOmittedKeySupport = false) { - if (in_array($scalar[$i], $stringDelimiters)) { + if (\in_array($scalar[$i], array('"', "'"))) { // quoted scalar $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { $tmp = ltrim(substr($scalar, $i), ' '); - if (!in_array($tmp[0], $delimiters)) { - throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } } } else { // "normal" string if (!$delimiters) { $output = substr($scalar, $i); - $i += strlen($output); + $i += \strlen($output); // remove comments if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { $output = substr($output, 0, $match[0][1]); } - } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + } elseif (Parser::preg_match('/^(.'.($legacyOmittedKeySupport ? '+' : '*').'?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; - $i += strlen($output); + $i += \strlen($output); } else { - throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar)); + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); } // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { - @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED); + 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); + } - // to be thrown in 3.0 - // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0])); + if ($output && '%' === $output[0]) { + @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); } if ($evaluate) { - $output = self::evaluateScalar($output, $references); + $output = self::evaluateScalar($output, $flags, $references); } } @@ -283,10 +378,10 @@ class Inline private static function parseQuotedScalar($scalar, &$i) { if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { - throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i))); + throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } - $output = substr($match[0], 1, strlen($match[0]) - 2); + $output = substr($match[0], 1, \strlen($match[0]) - 2); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { @@ -295,7 +390,7 @@ class Inline $output = $unescaper->unescapeSingleQuotedString($output); } - $i += strlen($match[0]); + $i += \strlen($match[0]); return $output; } @@ -304,6 +399,7 @@ class Inline * Parses a YAML sequence. * * @param string $sequence + * @param int $flags * @param int &$i * @param array $references * @@ -311,58 +407,68 @@ class Inline * * @throws ParseException When malformed inline YAML string is parsed */ - private static function parseSequence($sequence, &$i = 0, $references = array()) + private static function parseSequence($sequence, $flags, &$i = 0, $references = array()) { $output = array(); - $len = strlen($sequence); + $len = \strlen($sequence); ++$i; // [foo, bar, ...] while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); switch ($sequence[$i]) { case '[': // nested sequence - $output[] = self::parseSequence($sequence, $i, $references); + $value = self::parseSequence($sequence, $flags, $i, $references); break; case '{': // nested mapping - $output[] = self::parseMapping($sequence, $i, $references); - break; - case ']': - return $output; - case ',': - case ' ': + $value = self::parseMapping($sequence, $flags, $i, $references); break; default: - $isQuoted = in_array($sequence[$i], array('"', "'")); - $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); + $isQuoted = \in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, $flags, array(',', ']'), $i, null === $tag, $references); // the value can be an array if a reference has been resolved to an array var - if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { + if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) { // embedded mapping? try { $pos = 0; - $value = self::parseMapping('{'.$value.'}', $pos, $references); + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); } catch (\InvalidArgumentException $e) { // no, it's not } } - $output[] = $value; - --$i; } + if (null !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + ++$i; } - throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence)); + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); } /** * Parses a YAML mapping. * * @param string $mapping + * @param int $flags * @param int &$i * @param array $references * @@ -370,11 +476,12 @@ class Inline * * @throws ParseException When malformed inline YAML string is parsed */ - private static function parseMapping($mapping, &$i = 0, $references = array()) + private static function parseMapping($mapping, $flags, &$i = 0, $references = array()) { $output = array(); - $len = strlen($mapping); + $len = \strlen($mapping); ++$i; + $allowOverwrite = false; // {foo: bar, bar:foo, ...} while ($i < $len) { @@ -392,72 +499,122 @@ class Inline } // key - $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + $isKeyQuoted = \in_array($mapping[$i], array('"', "'"), true); + $key = self::parseScalar($mapping, $flags, array(':', ' '), $i, false, array(), true); + + if (':' !== $key && false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (':' === $key) { + @trigger_error(self::getDeprecationMessage('Omitting the key of a mapping is deprecated and will throw a ParseException in 4.0.'), E_USER_DEPRECATED); + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + @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); + } + } - // value - $done = false; + if (':' !== $key && !$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], array(' ', ',', '[', ']', '{', '}'), true))) { + @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); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); switch ($mapping[$i]) { case '[': // nested sequence - $value = self::parseSequence($mapping, $i, $references); + $value = self::parseSequence($mapping, $flags, $i, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (!isset($output[$key])) { - $output[$key] = $value; + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + @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); } - $done = true; break; case '{': // nested mapping - $value = self::parseMapping($mapping, $i, $references); + $value = self::parseMapping($mapping, $flags, $i, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (!isset($output[$key])) { - $output[$key] = $value; + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + @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); } - $done = true; - break; - case ':': - case ' ': break; default: - $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); + $value = self::parseScalar($mapping, $flags, array(',', '}'), $i, null === $tag, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (!isset($output[$key])) { - $output[$key] = $value; + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + @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); } - $done = true; --$i; } - ++$i; - if ($done) { - continue 2; - } + continue 2; } } - throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping)); + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); } /** * Evaluates scalars and replaces magic values. * * @param string $scalar + * @param int $flags * @param array $references * - * @return string A YAML string + * @return mixed The evaluated YAML string * * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved */ - private static function evaluateScalar($scalar, $references = array()) + private static function evaluateScalar($scalar, $flags, $references = array()) { $scalar = trim($scalar); $scalarLower = strtolower($scalar); @@ -471,11 +628,11 @@ class Inline // an unquoted * if (false === $value || '' === $value) { - throw new ParseException('A reference must contain at least one character.'); + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if (!array_key_exists($value, $references)) { - throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } return $references[$value]; @@ -490,35 +647,97 @@ class Inline return true; case 'false' === $scalarLower: return false; - // Optimise for returning strings. - case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): + case '!' === $scalar[0]: switch (true) { case 0 === strpos($scalar, '!str'): + @trigger_error(self::getDeprecationMessage('Support for the !str tag is deprecated since Symfony 3.4. Use the !!str tag instead.'), E_USER_DEPRECATED); + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '!!str '): + return (string) substr($scalar, 6); case 0 === strpos($scalar, '! '): - return (int) self::parseScalar(substr($scalar, 2)); + @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); + + return (int) self::parseScalar(substr($scalar, 2), $flags); case 0 === strpos($scalar, '!php/object:'): if (self::$objectSupport) { + @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); + return unserialize(substr($scalar, 12)); } if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.'); + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return; case 0 === strpos($scalar, '!!php/object:'): if (self::$objectSupport) { + @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); + return unserialize(substr($scalar, 13)); } if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.'); + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return; + case 0 === strpos($scalar, '!php/object'): + if (self::$objectSupport) { + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return; + case 0 === strpos($scalar, '!php/const:'): + if (self::$constantSupport) { + @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); + + if (\defined($const = substr($scalar, 11))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return; + case 0 === strpos($scalar, '!php/const'): + if (self::$constantSupport) { + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return; case 0 === strpos($scalar, '!!float '): return (float) substr($scalar, 8); + case 0 === strpos($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + default: + @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); + } + + // Optimize for returning strings. + // no break + case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): + switch (true) { + case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar): + $scalar = str_replace('_', '', (string) $scalar); + // omitting the break / return as integers are handled in the next case + // no break case ctype_digit($scalar): $raw = $scalar; $cast = (int) $scalar; @@ -531,15 +750,27 @@ class Inline return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); case is_numeric($scalar): case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; case '.inf' === $scalarLower: case '.nan' === $scalarLower: return -log(0); case '-.inf' === $scalarLower: return log(0); - case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): - return (float) str_replace(',', '', $scalar); + case Parser::preg_match('/^(-|\+)?[0-9][0-9,]*(\.[0-9_]+)?$/', $scalar): + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + if (false !== strpos($scalar, ',')) { + @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); + } + + return (float) str_replace(array(',', '_'), '', $scalar); case Parser::preg_match(self::getTimestampRegex(), $scalar): + if (Yaml::PARSE_DATETIME & $flags) { + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + return new \DateTime($scalar, new \DateTimeZone('UTC')); + } + $timeZone = date_default_timezone_get(); date_default_timezone_set('UTC'); $time = strtotime($scalar); @@ -547,9 +778,75 @@ class Inline return $time; } - default: - return (string) $scalar; } + + return (string) $scalar; + } + + /** + * @param string $value + * @param int &$i + * @param int $flags + * + * @return string|null + */ + private static function parseTag($value, &$i, $flags) + { + if ('!' !== $value[$i]) { + return; + } + + $tagLength = strcspn($value, " \t\n", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + // Is followed by a scalar + if ((!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], array('[', '{'), true)) && 'tagged' !== $tag) { + // Manage non-whitelisted scalars in {@link self::evaluateScalar()} + return; + } + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + $i = $nextOffset; + + return $tag; + } + + 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); + } + + /** + * @param string $scalar + * + * @return string + * + * @internal + */ + public static function evaluateBinaryScalar($scalar) + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + 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); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString($value) + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); } /** @@ -584,6 +881,21 @@ EOF; */ private static function getHexRegex() { - return '~^0x[0-9a-f]++$~i'; + return '~^0x[0-9a-f_]++$~i'; + } + + private static function getDeprecationMessage($message) + { + $message = rtrim($message, '.'); + + if (null !== self::$parsedFilename) { + $message .= ' in '.self::$parsedFilename; + } + + if (-1 !== self::$parsedLineNumber) { + $message .= ' on line '.(self::$parsedLineNumber + 1); + } + + return $message.'.'; } }