Merge branch '3.4' into 4.4

* 3.4:
  [PropertyAccess] fix tests
  [WebProfilerBundle] fix test
  remove assertions that can never be reached
  [PropertyAccess] Improve message of unitialized property in php 7.4
  [HttpFoundation] Fixed session migration with custom cookie lifetime
  [Serializer] Remove unused variable
  Allow URL-encoded special characters in basic auth part of URLs
  [Serializer] Fix unitialized properties (from PHP 7.4.2) when serializing context for the cache key
  [Validator] Add missing Ukrainian and Russian translations
  No need to reconnect the bags to the session
  Support for Content Security Policy style-src-elem and script-src-elem in WebProfiler
  [PropertyInfo][ReflectionExtractor] Check the array mutator prefixes last when the property is singular
This commit is contained in:
Nicolas Grekas 2020-04-06 12:16:26 +02:00
commit f72dd9cafa
17 changed files with 104 additions and 20 deletions

View File

@ -124,7 +124,7 @@ class ContentSecurityPolicyHandler
$headers = $this->getCspHeaders($response);
foreach ($headers as $header => $directives) {
foreach (['script-src' => 'csp_script_nonce', 'style-src' => 'csp_style_nonce'] as $type => $tokenName) {
foreach (['script-src' => 'csp_script_nonce', 'script-src-elem' => 'csp_script_nonce', 'style-src' => 'csp_style_nonce', 'style-src-elem' => 'csp_style_nonce'] as $type => $tokenName) {
if ($this->authorizesInline($directives, $type)) {
continue;
}

View File

@ -41,7 +41,7 @@ class ContentSecurityPolicyHandlerTest extends TestCase
$this->assertFalse($response->headers->has('X-SymfonyProfiler-Style-Nonce'));
foreach ($expectedCsp as $header => $value) {
$this->assertSame($value, $response->headers->get($header));
$this->assertSame($value, $response->headers->get($header), $header);
}
}
@ -131,7 +131,7 @@ class ContentSecurityPolicyHandlerTest extends TestCase
['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce],
$this->createRequest(),
$this->createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'']),
['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null],
['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src-elem \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src-elem \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null],
],
[
$nonce,

View File

@ -215,8 +215,10 @@ class NativeSessionStorage implements SessionStorageInterface
return false;
}
if (null !== $lifetime) {
if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) {
$this->save();
ini_set('session.cookie_lifetime', $lifetime);
$this->start();
}
if ($destroy) {
@ -225,10 +227,6 @@ class NativeSessionStorage implements SessionStorageInterface
$isRegenerated = session_regenerate_id($destroy);
// The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
// @see https://bugs.php.net/70013
$this->loadSession();
if (null !== $this->emulateSameSite) {
$originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
if (null !== $originalCookie) {

View File

@ -120,6 +120,19 @@ class NativeSessionStorageTest extends TestCase
$this->assertEquals(11, $storage->getBag('attributes')->get('legs'));
}
public function testRegenerateWithCustomLifetime()
{
$storage = $this->getStorage();
$storage->start();
$id = $storage->getId();
$lifetime = 999999;
$storage->getBag('attributes')->set('legs', 11);
$storage->regenerate(false, $lifetime);
$this->assertNotEquals($id, $storage->getId());
$this->assertEquals(11, $storage->getBag('attributes')->get('legs'));
$this->assertEquals($lifetime, ini_get('session.cookie_lifetime'));
}
public function testSessionGlobalIsUpToDateAfterIdRegeneration()
{
$storage = $this->getStorage();

View File

@ -44,7 +44,7 @@ class OptionsResolverIntrospectorTest extends TestCase
$resolver->setDefined($option = 'foo');
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getDefault($option));
$debug->getDefault($option);
}
public function testGetDefaultThrowsOnNotDefinedOption()
@ -54,7 +54,7 @@ class OptionsResolverIntrospectorTest extends TestCase
$resolver = new OptionsResolver();
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getDefault('foo'));
$debug->getDefault('foo');
}
public function testGetLazyClosures()
@ -75,7 +75,7 @@ class OptionsResolverIntrospectorTest extends TestCase
$resolver->setDefined($option = 'foo');
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getLazyClosures($option));
$debug->getLazyClosures($option);
}
public function testGetLazyClosuresThrowsOnNotDefinedOption()
@ -85,7 +85,7 @@ class OptionsResolverIntrospectorTest extends TestCase
$resolver = new OptionsResolver();
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getLazyClosures('foo'));
$debug->getLazyClosures('foo');
}
public function testGetAllowedTypes()

View File

@ -385,7 +385,7 @@ class PropertyAccessor implements PropertyAccessorInterface
} catch (\TypeError $e) {
// handle uninitialized properties in PHP >= 7
if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of the type (\w+), null returned$/', preg_quote(\get_class($object)), $access[self::ACCESS_NAME])), $e->getMessage(), $matches)) {
throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Have you forgotten to initialize a property or to make the return type nullable using "?%3$s" instead?', \get_class($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e);
throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', \get_class($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e);
}
throw $e;
@ -418,7 +418,7 @@ class PropertyAccessor implements PropertyAccessorInterface
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
$r = new \ReflectionProperty($matches[1], $matches[2]);
throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%3$s". You should either initialize it or make it nullable using "?%3$s" instead.', $r->getDeclaringClass()->getName(), $r->getName(), $r->getType()->getName()), 0, $e);
throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $r->getDeclaringClass()->getName(), $r->getName(), $r->getType()->getName()), 0, $e);
}
throw $e;

View File

@ -139,7 +139,7 @@ class PropertyAccessorTest extends TestCase
public function testGetValueThrowsExceptionIfUninitializedProperty()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should either initialize it or make it nullable using "?string" instead.');
$this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should initialize it or declare a default value instead.');
$this->propertyAccessor->getValue(new UninitializedProperty(), 'uninitialized');
}
@ -150,7 +150,7 @@ class PropertyAccessorTest extends TestCase
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
$this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty::getUninitialized()" returned "null", but expected type "array". Have you forgotten to initialize a property or to make the return type nullable using "?array" instead?');
$this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?');
$this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized');
}

View File

@ -58,6 +58,9 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
private $enableConstructorExtraction;
private $accessFlags;
private $arrayMutatorPrefixesFirst;
private $arrayMutatorPrefixesLast;
/**
* @param string[]|null $mutatorPrefixes
* @param string[]|null $accessorPrefixes
@ -70,6 +73,9 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$this->arrayMutatorPrefixes = null !== $arrayMutatorPrefixes ? $arrayMutatorPrefixes : self::$defaultArrayMutatorPrefixes;
$this->enableConstructorExtraction = $enableConstructorExtraction;
$this->accessFlags = $accessFlags;
$this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
$this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
}
/**
@ -405,7 +411,9 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$ucProperty = ucfirst($property);
$ucSingulars = (array) Inflector::singularize($ucProperty);
foreach ($this->mutatorPrefixes as $prefix) {
$mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
foreach ($mutatorPrefixes as $prefix) {
$names = [$ucProperty];
if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
$names = array_merge($names, $ucSingulars);

View File

@ -70,6 +70,7 @@ class ReflectionExtractorTest extends TestCase
'realParent',
'xTotals',
'YT',
'date',
'c',
'd',
'e',
@ -109,6 +110,7 @@ class ReflectionExtractorTest extends TestCase
'foo4',
'foo5',
'files',
'date',
'c',
'd',
'e',
@ -173,6 +175,8 @@ class ReflectionExtractorTest extends TestCase
['staticSetter', null],
['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')]],
['realParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]],
['date', [new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTime::class)]],
['dates', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTime::class))]],
];
}

View File

@ -210,4 +210,12 @@ class Dummy extends ParentDummy
public function getYT()
{
}
public function setDate(\DateTime $date)
{
}
public function addDate(\DateTime $date)
{
}
}

View File

@ -482,7 +482,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
*/
protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, $parameterName, $parameterData, array $context, $format = null)
{
if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) {
if (null === $this->propertyTypeExtractor || null === $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) {
return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format);
}
@ -617,6 +617,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
unset($context[$key]);
}
unset($context[self::EXCLUDE_FROM_CACHE_KEY]);
unset($context[self::OBJECT_TO_POPULATE]);
unset($context['cache_key']); // avoid artificially different keys
try {

View File

@ -174,6 +174,14 @@ class AbstractObjectNormalizerTest extends TestCase
$this->assertEquals('bar', $stringCollection->children[1]);
}
public function testDenormalizeNotSerializableObjectToPopulate()
{
$normalizer = new AbstractObjectNormalizerDummy();
$normalizedData = $normalizer->denormalize(['foo' => 'foo'], Dummy::class, null, [AbstractObjectNormalizer::OBJECT_TO_POPULATE => new NotSerializable()]);
$this->assertSame('foo', $normalizedData->foo);
}
private function getDenormalizerForStringCollection()
{
$extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock();
@ -448,3 +456,15 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn
$this->serializer = $serializer;
}
}
class NotSerializable
{
public function __sleep()
{
if (class_exists(\Error::class)) {
throw new \Error('not serializable');
}
throw new \Exception('not serializable');
}
}

View File

@ -24,7 +24,7 @@ class UrlValidator extends ConstraintValidator
{
const PATTERN = '~^
(%s):// # protocol
(([\_\.\pL\pN-]+:)?([\_\.\pL\pN-]+)@)? # basic auth
(((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth
(
([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
| # or

View File

@ -370,6 +370,18 @@
<source>This value is not a valid hostname.</source>
<target>Значение не является корректным именем хоста.</target>
</trans-unit>
<trans-unit id="96">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>Количество элементов в этой коллекции должно быть кратным {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="97">
<source>This value should satisfy at least one of the following constraints:</source>
<target>Значение должно удовлетворять как минимум одному из следующих ограничений:</target>
</trans-unit>
<trans-unit id="98">
<source>Each element of this collection should satisfy its own set of constraints.</source>
<target>Каждый элемент этой коллекции должен удовлетворять своему собственному набору ограничений.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -366,6 +366,22 @@
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Значення має бути між {{ min }} та {{ max }}.</target>
</trans-unit>
<trans-unit id="95">
<source>This value is not a valid hostname.</source>
<target>Значення не є дійсним іменем хоста.</target>
</trans-unit>
<trans-unit id="96">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>Кількість елементів у цій колекції повинна бути кратною {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="97">
<source>This value should satisfy at least one of the following constraints:</source>
<target>Значення повинно задовольняти хоча б одному з наступних обмежень:</target>
</trans-unit>
<trans-unit id="98">
<source>Each element of this collection should satisfy its own set of constraints.</source>
<target>Кожен елемент цієї колекції повинен задовольняти власному набору обмежень.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -157,6 +157,8 @@ class UrlValidatorTest extends ConstraintValidatorTestCase
['http://user.name:pass.word@symfony.com'],
['http://user-name@symfony.com'],
['http://user_name@symfony.com'],
['http://u%24er:password@symfony.com'],
['http://user:pa%24%24word@symfony.com'],
['http://symfony.com?'],
['http://symfony.com?query=1'],
['http://symfony.com/?query=1'],
@ -255,6 +257,8 @@ class UrlValidatorTest extends ConstraintValidatorTestCase
['http://:password@@symfony.com'],
['http://username:passwordsymfony.com'],
['http://usern@me:password@symfony.com'],
['http://nota%hex:password@symfony.com'],
['http://username:nota%hex@symfony.com'],
['http://example.com/exploit.html?<script>alert(1);</script>'],
['http://example.com/exploit.html?hel lo'],
['http://example.com/exploit.html?not_a%hex'],

View File

@ -615,7 +615,7 @@ class Inline
throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;