8000 Fixed YAML Parser does not ignore duplicate keys, violating YAML spec. · symfony/symfony@951acca · GitHub
[go: up one dir, main page]

Skip to content

Commit 951acca

Browse files
committed
Fixed YAML Parser does not ignore duplicate keys, violating YAML spec.
The current [YAML 1.2] specification clearly states: > JSON's RFC4627 requires that mappings keys merely “SHOULD” be unique, while YAML insists they “MUST” be. The outdated [YAML 1.1] spec contained a crystal clear note on how the error of duplicate keys is to be handled by parsers, which is (sadly) no longer contained in the latest 1.2 spec (only leaving the requirement): > It is an error for two equal keys to appear in the same mapping node. In such a case the YAML processor may continue, ignoring the second `key: value` pair and issuing an appropriate warning. This strategy preserves a consistent information model for one-pass and random access applications. [YAML 1.2]: http://yaml.org/spec/1.2/spec.html#id2759572 [YAML 1.1]: http://yaml.org/spec/1.1/#id932806
1 parent c0187c1 commit 951acca

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

src/Symfony/Component/Yaml/Inline.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,19 +350,37 @@ private static function parseMapping($mapping, &$i = 0)
350350
switch ($mapping[$i]) {
351351
case '[':
352352
// nested sequence
353-
$output[$key] = self::parseSequence($mapping, $i);
353+
$value = self::parseSequence($mapping, $i);
354+
// Spec: Keys MUST be unique; first one wins.
355+
// Parser cannot abort this mapping earlier, since lines
356+
// are processed sequentially.
357+
if (!isset($output[$key])) {
358+
$output[$key] = $value;
359+
}
354360
$done = true;
355361
break;
356362
case '{':
357363
// nested mapping
358-
$output[$key] = self::parseMapping($mapping, $i);
364+
$value = self::parseMapping($mapping, $i);
365+
// Spec: Keys MUST be unique; first one wins.
366+
// Parser cannot abort this mapping earlier, since lines
367+
// are processed sequentially.
368+
if (!isset($output[$key])) {
369+
$output[$key] = $value;
370+
}
359371
$done = true;
360372
break;
361373
case ':':
362374
case ' ':
363375
break;
364376
default:
365-
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
377+
$value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
378+
// Spec: Keys MUST be unique; first one wins.
379+
// Parser cannot abort this mapping earlier, since lines
380+
// are processed sequentially.
381+
if (!isset($output[$key])) {
382+
$output[$key] = $value;
383+
}
366384
$done = true;
367385
--$i;
368386
}

src/Symfony/Component/Yaml/Parser.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,35 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
178178
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
179179
// if next line is less indented or equal, then it means that the current value is null
180180
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
181-
$data[$key] = null;
181+
// Spec: Keys MUST be unique; first one wins.
182+
// Parser cannot abort this mapping earlier, since lines
183+
// are processed sequentially.
184+
if (!isset($data[$key])) {
185+
$data[$key] = null;
186+
}
182187
} else {
183188
$c = $this->getRealCurrentLineNb() + 1;
184189
$parser = new Parser($c);
185190
$parser->refs =& $this->refs;
186-
$data[$key] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
191+
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
192+
// Spec: Keys MUST be unique; first one wins.
193+
// Parser cannot abort this mapping earlier, since lines
194+
// are processed sequentially.
195+
if (!isset($data[$key])) {
196+
$data[$key] = $value;
197+
}
187198
}
188199
} else {
189200
if ($isInPlace) {
190201
$data = $this->refs[$isInPlace];
191202
} else {
192-
$data[$key] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
203+
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);;
204+
// Spec: Keys MUST be unique; first one wins.
205+
// Parser cannot abort this mapping earlier, since lines
206+
// are processed sequentially.
207+
if (!isset($data[$key])) {
208+
$data[$key] = $value;
209+
}
193210
}
194211
}
195212
} else {

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,53 @@ public function testMappingInASequence()
508508
);
509509
}
510510

511+
/**
512+
* > It is an error for two equal keys to appear in the same mapping node.
513+
* > In such a case the YAML processor may continue, ignoring the second
514+
* > `key: value` pair and issuing an appropriate warning. This strategy
515+
* > preserves a consistent information model for one-pass and random access
516+
* > applications.
517+
*
518+
* @see http://yaml.org/spec/1.2/spec.html#id2759572
519+
* @see http://yaml.org/spec/1.1/#id932806
520+
*
521+
* @covers \Symfony\Component\Yaml\Parser::parse
522+
*/
523+
public function testMappingDuplicateKeyBlock()
524+
{
525+
$input = <<<EOD
526+
parent:
527+
child: first
528+
child: duplicate
529+
parent:
530+
child: duplicate
531+
child: duplicate
532+
EOD;
533+
$expected = array(
534+
'parent' => array(
535+
'child' => 'first',
536+
),
537+
);
538+
$this->assertSame($expected, Yaml::parse($input));
539+
}
540+
541+
/**
542+
* @covers \Symfony\Component\Yaml\Inline::parseMapping
543+
*/
544+
public function testMappingDuplicateKeyFlow()
545+
{
546+
$input = <<<EOD
547+
parent: { child: first, child: duplicate }
548+
parent: { child: duplicate, child: duplicate }
549+
EOD;
550+
$expected = array(
551+
'parent' => array(
552+
'child' => 'first',
553+
),
554+
);
555+
$this->assertSame($expected, Yaml::parse($input));
556+
}
557+
511558
public function testEmptyValue()
512559
{
513560
$input = <<<EOF

0 commit comments

Comments
 (0)
0