Skip to content

Commit c74f1aa

Browse files
committed
feature #17863 [Yaml] add support for parsing the !!binary tag (xabbuh)
This PR was merged into the 3.1-dev branch. Discussion ---------- [Yaml] add support for parsing the !!binary tag | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #8094, #15587, #17599 | License | MIT | Doc PR | TODO Commits ------- 79a63d5 [Yaml] add support for the !!binary tag
2 parents a0fd637 + 79a63d5 commit c74f1aa

File tree

8 files changed

+181
-2
lines changed

8 files changed

+181
-2
lines changed

src/Symfony/Component/Yaml/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ CHANGELOG
44
3.1.0
55
-----
66

7+
* Added support for parsing base64 encoded binary data when they are tagged
8+
with the `!!binary` tag.
9+
10+
* Added support for dumping binary data as base64 encoded strings by passing
11+
the `Yaml::DUMP_BASE64_BINARY_DATA` flag.
12+
713
* Added support for parsing timestamps as `\DateTime` objects:
814

915
```php

src/Symfony/Component/Yaml/Inline.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ public static function dump($value, $flags = 0)
197197
return $repr;
198198
case '' == $value:
199199
return "''";
200+
case Yaml::DUMP_BASE64_BINARY_DATA & $flags && self::isBinaryString($value):
201+
return '!!binary '.base64_encode($value);
200202
case Escaper::requiresDoubleQuoting($value):
201203
return Escaper::escapeWithDoubleQuotes($value);
202204
case Escaper::requiresSingleQuoting($value):
@@ -576,6 +578,8 @@ private static function evaluateScalar($scalar, $flags, $references = array())
576578
return -log(0);
577579
case '-.inf' === $scalarLower:
578580
return log(0);
581+
case 0 === strpos($scalar, '!!binary '):
582+
return self::evaluateBinaryScalar(substr($scalar, 9));
579583
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
580584
return (float) str_replace(',', '', $scalar);
581585
case preg_match(self::getTimestampRegex(), $scalar):
@@ -595,6 +599,33 @@ private static function evaluateScalar($scalar, $flags, $references = array())
595599
}
596600
}
597601

602+
/**
603+
* @param string $scalar
604+
*
605+
* @return string
606+
*
607+
* @internal
608+
*/
609+
public static function evaluateBinaryScalar($scalar)
610+
{
611+
$parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
612+
613+
if (0 !== (strlen($parsedBinaryData) % 4)) {
614+
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)));
615+
}
616+
617+
if (!preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
618+
throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData));
619+
}
620+
621+
return base64_decode($parsedBinaryData, true);
622+
}
623+
624+
private static function isBinaryString($value)
625+
{
626+
return preg_match('/[^\x09-\x0d\x20-\xff]/', $value);
627+
}
628+
598629
/**
599630
* Gets a regex that matches a YAML date.
600631
*

src/Symfony/Component/Yaml/Parser.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
class Parser
2222
{
23+
const TAG_PATTERN = '((?P<tag>![\w!.\/:-]+) +)?';
2324
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
2425

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

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

522-
return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
523+
$data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
524+
525+
if (isset($matches['tag']) && '!!binary' === $matches['tag']) {
526+
return Inline::evaluateBinaryScalar($data);
527+
}
528+
529+
return $data;
523530
}
524531

525532
try {

src/Symfony/Component/Yaml/Tests/DumperTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,22 @@ public function getEscapeSequences()
276276
'paragraph-separator' => array("\t\\P", '"\t\\\\P"'),
277277
);
278278
}
279+
280+
public function testBinaryDataIsDumpedAsIsWithoutFlag()
281+
{
282+
$binaryData = file_get_contents(__DIR__.'/Fixtures/arrow.gif');
283+
$expected = "{ data: '".str_replace("'", "''", $binaryData)."' }";
284+
285+
$this->assertSame($expected, $this->dumper->dump(array('data' => $binaryData)));
286+
}
287+
288+
public function testBinaryDataIsDumpedBase64EncodedWithFlag()
289+
{
290+
$binaryData = file_get_contents(__DIR__.'/Fixtures/arrow.gif');
291+
$expected = '{ data: !!binary '.base64_encode($binaryData).' }';
292+
293+
$this->assertSame($expected, $this->dumper->dump(array('data' => $binaryData), 0, 0, Yaml::DUMP_BASE64_BINARY_DATA));
294+
}
279295
}
280296

281297
class A
185 Bytes
Loading

src/Symfony/Component/Yaml/Tests/InlineTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,4 +532,41 @@ public function getDateTimeDumpTests()
532532

533533
return $tests;
534534
}
535+
536+
/**
537+
* @dataProvider getBinaryData
538+
*/
539+
public function testParseBinaryData($data)
540+
{
541+
$this->assertSame('Hello world', Inline::parse($data));
542+
}
543+
544+
public function getBinaryData()
545+
{
546+
return array(
547+
'enclosed with double quotes' => array('!!binary "SGVsbG8gd29ybGQ="'),
548+
'enclosed with single quotes' => array("!!binary 'SGVsbG8gd29ybGQ='"),
549+
'containing spaces' => array('!!binary "SGVs bG8gd 29ybGQ="'),
550+
);
551+
}
552+
553+
/**
554+
* @dataProvider getInvalidBinaryData
555+
*/
556+
public function testParseInvalidBinaryData($data, $expectedMessage)
557+
{
558+
$this->setExpectedExceptionRegExp('\Symfony\Component\Yaml\Exception\ParseException', $expectedMessage);
559+
560+
Inline::parse($data);
561+
}
562+
563+
public function getInvalidBinaryData()
564+
{
565+
return array(
566+
'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\)/'),
567+
'invalid characters' => array('!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'),
568+
'too many equals characters' => array('!!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'),
569+
'misplaced equals character' => array('!!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'),
570+
);
571+
}
535572
}

src/Symfony/Component/Yaml/Tests/ParserTest.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,87 @@ public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks()
11201120
$this->parser->parse($yaml)
11211121
);
11221122
}
1123+
1124+
/**
1125+
* @dataProvider getBinaryData
1126+
*/
1127+
public function testParseBinaryData($data)
1128+
{
1129+
$this->assertSame(array('data' => 'Hello world'), $this->parser->parse($data));
1130+
}
1131+
1132+
public function getBinaryData()
1133+
{
1134+
return array(
1135+
'enclosed with double quotes' => array('data: !!binary "SGVsbG8gd29ybGQ="'),
1136+
'enclosed with single quotes' => array("data: !!binary 'SGVsbG8gd29ybGQ='"),
1137+
'containing spaces' => array('data: !!binary "SGVs bG8gd 29ybGQ="'),
1138+
'in block scalar' => array(
1139+
<<<EOT
1140+
data: !!binary |
1141+
SGVsbG8gd29ybGQ=
1142+
EOT
1143+
),
1144+
'containing spaces in block scalar' => array(
1145+
<<<EOT
1146+
data: !!binary |
1147+
SGVs bG8gd 29ybGQ=
1148+
EOT
1149+
),
1150+
);
1151+
}
1152+
1153+
/**
1154+
* @dataProvider getInvalidBinaryData
1155+
*/
1156+
public function testParseInvalidBinaryData($data, $expectedMessage)
1157+
{
1158+
$this->setExpectedExceptionRegExp('\Symfony\Component\Yaml\Exception\ParseException', $expectedMessage);
1159+
1160+
$this->parser->parse($data);
1161+
}
1162+
1163+
public function getInvalidBinaryData()
1164+
{
1165+
return array(
1166+
'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\)/'),
1167+
'invalid characters' => array('!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'),
1168+
'too many equals characters' => array('data: !!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'),
1169+
'misplaced equals character' => array('data: !!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'),
1170+
'length not a multiple of four in block scalar' => array(
1171+
<<<EOT
1172+
data: !!binary |
1173+
SGVsbG8d29ybGQ=
1174+
EOT
1175+
,
1176+
'/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/',
1177+
),
1178+
'invalid characters in block scalar' => array(
1179+
<<<EOT
1180+
data: !!binary |
1181+
SGVsbG8#d29ybGQ=
1182+
EOT
1183+
,
1184+
'/The base64 encoded data \(.*\) contains invalid characters/',
1185+
),
1186+
'too many equals characters in block scalar' => array(
1187+
<<<EOT
1188+
data: !!binary |
1189+
SGVsbG8gd29yb===
1190+
EOT
1191+
,
1192+
'/The base64 encoded data \(.*\) contains invalid characters/',
1193+
),
1194+
'misplaced equals character in block scalar' => array(
1195+
<<<EOT
1196+
data: !!binary |
1197+
SGVsbG8gd29ybG=Q
1198+
EOT
1199+
,
1200+
'/The base64 encoded data \(.*\) contains invalid characters/',
1201+
),
1202+
);
1203+
}
11231204
}
11241205

11251206
class B

src/Symfony/Component/Yaml/Yaml.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Yaml
2626
const PARSE_OBJECT_FOR_MAP = 8;
2727
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
2828
const PARSE_DATETIME = 32;
29+
const DUMP_BASE64_BINARY_DATA = 64;
2930

3031
/**
3132
* Parses YAML into a PHP value.

0 commit comments

Comments
 (0)