Skip to content

[Yaml] add support for parsing the !!binary tag #17863

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Symfony/Component/Yaml/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ CHANGELOG
3.1.0
-----

* Added support for parsing base64 encoded binary data when they are tagged
with the `!!binary` tag.

* Added support for dumping binary data as base64 encoded strings by passing
the `Yaml::DUMP_BASE64_BINARY_DATA` flag.

* Added support for parsing timestamps as `\DateTime` objects:

```php
Expand Down
31 changes: 31 additions & 0 deletions src/Symfony/Component/Yaml/Inline.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ public static function dump($value, $flags = 0)
return $repr;
case '' == $value:
return "''";
case Yaml::DUMP_BASE64_BINARY_DATA & $flags && self::isBinaryString($value):
return '!!binary '.base64_encode($value);
case Escaper::requiresDoubleQuoting($value):
return Escaper::escapeWithDoubleQuotes($value);
case Escaper::requiresSingleQuoting($value):
Expand Down Expand Up @@ -576,6 +578,8 @@ private static function evaluateScalar($scalar, $flags, $references = array())
return -log(0);
case '-.inf' === $scalarLower:
return log(0);
case 0 === strpos($scalar, '!!binary '):
return self::evaluateBinaryScalar(substr($scalar, 9));
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
return (float) str_replace(',', '', $scalar);
case preg_match(self::getTimestampRegex(), $scalar):
Expand All @@ -595,6 +599,33 @@ private static function evaluateScalar($scalar, $flags, $references = array())
}
}

/**
* @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)));
}

if (!preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData));
}

return base64_decode($parsedBinaryData, true);
}

private static function isBinaryString($value)
{
return preg_match('/[^\x09-\x0d\x20-\xff]/', $value);
}

/**
* Gets a regex that matches a YAML date.
*
Expand Down
11 changes: 9 additions & 2 deletions src/Symfony/Component/Yaml/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/
class Parser
{
const TAG_PATTERN = '((?P<tag>![\w!.\/:-]+) +)?';
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';

private $offset = 0;
Expand Down Expand Up @@ -516,10 +517,16 @@ private function parseValue($value, $flags, $context)
return $this->refs[$value];
}

if (preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
if (preg_match('/^'.self::TAG_PATTERN.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';

return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
$data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));

if (isset($matches['tag']) && '!!binary' === $matches['tag']) {
return Inline::evaluateBinaryScalar($data);
}

return $data;
}

try {
Expand Down
16 changes: 16 additions & 0 deletions src/Symfony/Component/Yaml/Tests/DumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ public function getEscapeSequences()
'paragraph-separator' => array("\t\\P", '"\t\\\\P"'),
);
}

public function testBinaryDataIsDumpedAsIsWithoutFlag()
{
$binaryData = file_get_contents(__DIR__.'/Fixtures/arrow.gif');
$expected = "{ data: '".str_replace("'", "''", $binaryData)."' }";

$this->assertSame($expected, $this->dumper->dump(array('data' => $binaryData)));
}

public function testBinaryDataIsDumpedBase64EncodedWithFlag()
{
$binaryData = file_get_contents(__DIR__.'/Fixtures/arrow.gif');
$expected = '{ data: !!binary '.base64_encode($binaryData).' }';

$this->assertSame($expected, $this->dumper->dump(array('data' => $binaryData), 0, 0, Yaml::DUMP_BASE64_BINARY_DATA));
}
}

class A
Expand Down
Binary file added src/Symfony/Component/Yaml/Tests/Fixtures/arrow.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions src/Symfony/Component/Yaml/Tests/InlineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -532,4 +532,41 @@ public function getDateTimeDumpTests()

return $tests;
}

/**
* @dataProvider getBinaryData
*/
public function testParseBinaryData($data)
{
$this->assertSame('Hello world', Inline::parse($data));
}

public function getBinaryData()
{
return array(
'enclosed with double quotes' => array('!!binary "SGVsbG8gd29ybGQ="'),
'enclosed with single quotes' => array("!!binary 'SGVsbG8gd29ybGQ='"),
'containing spaces' => array('!!binary "SGVs bG8gd 29ybGQ="'),
);
}

/**
* @dataProvider getInvalidBinaryData
*/
public function testParseInvalidBinaryData($data, $expectedMessage)
{
$this->setExpectedExceptionRegExp('\Symfony\Component\Yaml\Exception\ParseException', $expectedMessage);

Inline::parse($data);
}

public function getInvalidBinaryData()
{
return array(
'length not a multiple of four' => array('!!binary "SGVsbG8d29ybGQ="', '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/'),
'invalid characters' => array('!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'),
'too many equals characters' => array('!!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'),
'misplaced equals character' => array('!!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'),
);
}
}
81 changes: 81 additions & 0 deletions src/Symfony/Component/Yaml/Tests/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,87 @@ public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks()
$this->parser->parse($yaml)
);
}

/**
* @dataProvider getBinaryData
*/
public function testParseBinaryData($data)
{
$this->assertSame(array('data' => 'Hello world'), $this->parser->parse($data));
}

public function getBinaryData()
{
return array(
'enclosed with double quotes' => array('data: !!binary "SGVsbG8gd29ybGQ="'),
'enclosed with single quotes' => array("data: !!binary 'SGVsbG8gd29ybGQ='"),
'containing spaces' => array('data: !!binary "SGVs bG8gd 29ybGQ="'),
'in block scalar' => array(
<<<EOT
data: !!binary |
SGVsbG8gd29ybGQ=
EOT
),
'containing spaces in block scalar' => array(
<<<EOT
data: !!binary |
SGVs bG8gd 29ybGQ=
EOT
),
);
}

/**
* @dataProvider getInvalidBinaryData
*/
public function testParseInvalidBinaryData($data, $expectedMessage)
{
$this->setExpectedExceptionRegExp('\Symfony\Component\Yaml\Exception\ParseException', $expectedMessage);

$this->parser->parse($data);
}

public function getInvalidBinaryData()
{
return array(
'length not a multiple of four' => array('data: !!binary "SGVsbG8d29ybGQ="', '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/'),
'invalid characters' => array('!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'),
'too many equals characters' => array('data: !!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'),
'misplaced equals character' => array('data: !!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'),
'length not a multiple of four in block scalar' => array(
<<<EOT
data: !!binary |
SGVsbG8d29ybGQ=
EOT
,
'/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/',
),
'invalid characters in block scalar' => array(
<<<EOT
data: !!binary |
SGVsbG8#d29ybGQ=
EOT
,
'/The base64 encoded data \(.*\) contains invalid characters/',
),
'too many equals characters in block scalar' => array(
<<<EOT
data: !!binary |
SGVsbG8gd29yb===
EOT
,
'/The base64 encoded data \(.*\) contains invalid characters/',
),
'misplaced equals character in block scalar' => array(
<<<EOT
data: !!binary |
SGVsbG8gd29ybG=Q
EOT
,
'/The base64 encoded data \(.*\) contains invalid characters/',
),
);
}
}

class B
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Yaml/Yaml.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Yaml
const PARSE_OBJECT_FOR_MAP = 8;
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
const PARSE_DATETIME = 32;
const DUMP_BASE64_BINARY_DATA = 64;

/**
* Parses YAML into a PHP value.
Expand Down