8000 bug #10902 [Yaml] Fixed YAML Parser does not ignore duplicate keys, v… · symfony/symfony@0eddb84 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0eddb84

Browse files
committed
bug #10902 [Yaml] Fixed YAML Parser does not ignore duplicate keys, violating YAML spec. (sun)
This PR was merged into the 2.5-dev branch. Discussion ---------- [Yaml] Fixed YAML Parser does not ignore duplicate keys, violating YAML spec. | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | **mayhaps** | Deprecations? | no | Tests pass? | yes | Needs merge to | 2.4 | Fixed tickets | — | License | MIT | Doc PR | — 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 Commits ------- 951acca Fixed YAML Parser does not ignore duplicate keys, violating YAML spec.
2 parents a61fddb + 951acca commit 0eddb84

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