Skip to content

Commit 0a36e62

Browse files
Merge branch '3.4'
* 3.4: fix merge fix merge [FORM] Prevent forms from extending itself as a parent fix merge Fix 7.2 compat layer [DI] Prefixed env vars and load time inlining are incompatible bug #24499 [Bridge\PhpUnit] Fix infinite loop when running isolated method (bis) (nicolas-grekas) Fix PHP 7.2 support [HttpFoundation] Add missing session.lazy_write config option [DI] Exclude inline services declared in XML from autowiring candidates [HttpFoundation] Combine Cache-Control headers [Form] fix parsing invalid floating point numbers Escape command usage when displaying it in the text descriptor [DI] Throw accurate failures when accessing removed services [DI] Turn private defs to non-public ones before removing passes Use for=ID on radio/checkbox label.
2 parents e14a4b5 + 3dc3fd3 commit 0a36e62

File tree

8 files changed

+243
-14
lines changed

8 files changed

+243
-14
lines changed

Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public function reverseTransform($value)
116116
return;
117117
}
118118

119+
$position = 0;
119120
$formatter = $this->getNumberFormatter();
120121
$groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
121122
$decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
@@ -129,18 +130,44 @@ public function reverseTransform($value)
129130
$value = str_replace(',', $decSep, $value);
130131
}
131132

133+
if (false !== strpos($value, $decSep)) {
134+
$type = \NumberFormatter::TYPE_DOUBLE;
135+
} else {
136+
$type = \PHP_INT_SIZE === 8 ? \NumberFormatter::TYPE_INT64 : \NumberFormatter::TYPE_INT32;
137+
}
138+
132139
// replace normal spaces so that the formatter can read them
133-
$value = $formatter->parse(str_replace(' ', "\xc2\xa0", $value));
140+
$result = $formatter->parse(str_replace(' ', "\xc2\xa0", $value), $type, $position);
134141

135142
if (intl_is_failure($formatter->getErrorCode())) {
136143
throw new TransformationFailedException($formatter->getErrorMessage());
137144
}
138145

139146
if (self::FRACTIONAL == $this->type) {
140-
$value /= 100;
147+
$result /= 100;
141148
}
142149

143-
return $value;
150+
if (\function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value, null, true)) {
151+
$length = mb_strlen($value, $encoding);
152+
$remainder = mb_substr($value, $position, $length, $encoding);
153+
} else {
154+
$length = \strlen($value);
155+
$remainder = substr($value, $position, $length);
156+
}
157+
158+
// After parsing, position holds the index of the character where the
159+
// parsing stopped
160+
if ($position < $length) {
161+
// Check if there are unrecognized characters at the end of the
162+
// number (excluding whitespace characters)
163+
$remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0");
164+
165+
if ('' !== $remainder) {
166+
throw new TransformationFailedException(sprintf('The number contains unrecognized characters: "%s"', $remainder));
167+
}
168+
}
169+
170+
return $result;
144171
}
145172

146173
/**

FormRegistry.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Form;
1313

1414
use Symfony\Component\Form\Exception\ExceptionInterface;
15+
use Symfony\Component\Form\Exception\LogicException;
1516
use Symfony\Component\Form\Exception\UnexpectedTypeException;
1617
use Symfony\Component\Form\Exception\InvalidArgumentException;
1718

@@ -44,6 +45,8 @@ class FormRegistry implements FormRegistryInterface
4445
*/
4546
private $resolvedTypeFactory;
4647

48+
private $checkedTypes = array();
49+
4750
/**
4851
* @param FormExtensionInterface[] $extensions An array of FormExtensionInterface
4952
* @param ResolvedFormTypeFactoryInterface $resolvedTypeFactory The factory for resolved form types
@@ -106,18 +109,29 @@ private function resolveType(FormTypeInterface $type)
106109
$parentType = $type->getParent();
107110
$fqcn = get_class($type);
108111

109-
foreach ($this->extensions as $extension) {
110-
$typeExtensions = array_merge(
112+
if (isset($this->checkedTypes[$fqcn])) {
113+
$types = implode(' > ', array_merge(array_keys($this->checkedTypes), array($fqcn)));
114+
throw new LogicException(sprintf('Circular reference detected for form "%s" (%s).', $fqcn, $types));
115+
}
116+
117+
$this->checkedTypes[$fqcn] = true;
118+
119+
try {
120+
foreach ($this->extensions as $extension) {
121+
$typeExtensions = array_merge(
122+
$typeExtensions,
123+
$extension->getTypeExtensions($fqcn)
124+
);
125+
}
126+
127+
return $this->resolvedTypeFactory->createResolvedType(
128+
$type,
111129
$typeExtensions,
112-
$extension->getTypeExtensions($fqcn)
130+
$parentType ? $this->getType($parentType) : null
113131
);
132+
} finally {
133+
unset($this->checkedTypes[$fqcn]);
114134
}
115-
116-
return $this->resolvedTypeFactory->createResolvedType(
117-
$type,
118-
$typeExtensions,
119-
$parentType ? $this->getType($parentType) : null
120-
);
121135
}
122136

123137
/**

Tests/Extension/Core/DataTransformer/PercentToLocalizedStringTransformerTest.php

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ public function testDecimalSeparatorMayBeDotIfGroupingSeparatorIsNotDot()
141141
*/
142142
public function testDecimalSeparatorMayNotBeDotIfGroupingSeparatorIsDot()
143143
{
144-
// Since we test against "de_AT", we need the full implementation
144+
// Since we test against "de_DE", we need the full implementation
145145
IntlTestHelper::requireFullIntl($this, '4.8.1.1');
146146

147-
\Locale::setDefault('de_AT');
147+
\Locale::setDefault('de_DE');
148148

149149
$transformer = new PercentToLocalizedStringTransformer(1, 'integer');
150150

@@ -236,4 +236,70 @@ public function testDecimalSeparatorMayBeCommaIfGroupingSeparatorIsCommaButNoGro
236236
$this->assertEquals(1234.5, $transformer->reverseTransform('1234,5'));
237237
$this->assertEquals(1234.5, $transformer->reverseTransform('1234.5'));
238238
}
239+
240+
/**
241+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
242+
*/
243+
public function testReverseTransformDisallowsLeadingExtraCharacters()
244+
{
245+
$transformer = new PercentToLocalizedStringTransformer();
246+
247+
$transformer->reverseTransform('foo123');
248+
}
249+
250+
/**
251+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
252+
* @expectedExceptionMessage The number contains unrecognized characters: "foo3"
253+
*/
254+
public function testReverseTransformDisallowsCenteredExtraCharacters()
255+
{
256+
$transformer = new PercentToLocalizedStringTransformer();
257+
258+
$transformer->reverseTransform('12foo3');
259+
}
260+
261+
/**
262+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
263+
* @expectedExceptionMessage The number contains unrecognized characters: "foo8"
264+
* @requires extension mbstring
265+
*/
266+
public function testReverseTransformDisallowsCenteredExtraCharactersMultibyte()
267+
{
268+
// Since we test against other locales, we need the full implementation
269+
IntlTestHelper::requireFullIntl($this, false);
270+
271+
\Locale::setDefault('ru');
272+
273+
$transformer = new PercentToLocalizedStringTransformer();
274+
275+
$transformer->reverseTransform("12\xc2\xa0345,67foo8");
276+
}
277+
278+
/**
279+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
280+
* @expectedExceptionMessage The number contains unrecognized characters: "foo"
281+
*/
282+
public function testReverseTransformDisallowsTrailingExtraCharacters()
283+
{
284+
$transformer = new PercentToLocalizedStringTransformer();
285+
286+
$transformer->reverseTransform('123foo');
287+
}
288+
289+
/**
290+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
291+
* @expectedExceptionMessage The number contains unrecognized characters: "foo"
292+
* @requires extension mbstring
293+
*/
294+
public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte()
295+
{
296+
// Since we test against other locales, we need the full implementation
297+
IntlTestHelper::requireFullIntl($this, false);
298+
299+
\Locale::setDefault('ru');
300+
301+
$transformer = new PercentToLocalizedStringTransformer();
302+
303+
$transformer->reverseTransform("12\xc2\xa0345,678foo");
304+
}
239305
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Fixtures;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
16+
class FormWithSameParentType extends AbstractType
17+
{
18+
public function getParent()
19+
{
20+
return self::class;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Fixtures;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
16+
class RecursiveFormTypeBar extends AbstractType
17+
{
18+
public function getParent()
19+
{
20+
return RecursiveFormTypeBaz::class;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Fixtures;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
16+
class RecursiveFormTypeBaz extends AbstractType
17+
{
18+
public function getParent()
19+
{
20+
return RecursiveFormTypeFoo::class;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Fixtures;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
16+
class RecursiveFormTypeFoo extends AbstractType
17+
{
18+
public function getParent()
19+
{
20+
return RecursiveFormTypeBar::class;
21+
}
22+
}

Tests/FormRegistryTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
use Symfony\Component\Form\FormTypeGuesserChain;
1717
use Symfony\Component\Form\ResolvedFormType;
1818
use Symfony\Component\Form\ResolvedFormTypeFactoryInterface;
19+
use Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType;
20+
use Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBar;
21+
use Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBaz;
22+
use Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo;
1923
use Symfony\Component\Form\Tests\Fixtures\FooSubType;
2024
use Symfony\Component\Form\Tests\Fixtures\FooType;
2125
use Symfony\Component\Form\Tests\Fixtures\FooTypeBarExtension;
@@ -156,6 +160,36 @@ public function testGetTypeConnectsParent()
156160
$this->assertSame($resolvedType, $this->registry->getType(get_class($type)));
157161
}
158162

163+
/**
164+
* @expectedException \Symfony\Component\Form\Exception\LogicException
165+
* @expectedExceptionMessage Circular reference detected for form "Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType" (Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType > Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType).
166+
*/
167+
public function testFormCannotHaveItselfAsAParent()
168+
{
169+
$type = new FormWithSameParentType();
170+
171+
$this->extension2->addType($type);
172+
173+
$this->registry->getType(FormWithSameParentType::class);
174+
}
175+
176+
/**
177+
* @expectedException \Symfony\Component\Form\Exception\LogicException
178+
* @expectedExceptionMessage Circular reference detected for form "Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo" (Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo > Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBar > Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBaz > Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo).
179+
*/
180+
public function testRecursiveFormDependencies()
181+
{
182+
$foo = new RecursiveFormTypeFoo();
183+
$bar = new RecursiveFormTypeBar();
184+
$baz = new RecursiveFormTypeBaz();
185+
186+
$this->extension2->addType($foo);
187+
$this->extension2->addType($bar);
188+
$this->extension2->addType($baz);
189+
190+
$this->registry->getType(RecursiveFormTypeFoo::class);
191+
}
192+
159193
/**
160194
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
161195
*/

0 commit comments

Comments
 (0)