Merge branch '3.4' into 4.4

* 3.4:
  [Filesystem] Handle paths on different drives
  [WebProfiler] Do not add src-elem CSP directives if they do not exist
  [Yaml] fix parse error when unindented collections contain a comment
  [3.4][Inflector] Improve testSingularize() argument name
  [PhpUnitBridge] fix PHP 5.3 compat again
  Skip validation when email is an empty object
  fix sr_Latn translation
  [Validator] fix lazy property usage.
  Fix annotation
  [PhpUnitBridge] fix compat with PHP 5.3
  [DX] Show the ParseException message in YAML file loaders
This commit is contained in:
Nicolas Grekas 2020-05-04 16:02:18 +02:00
commit 394946de47
19 changed files with 151 additions and 53 deletions

View File

@ -129,12 +129,11 @@ class ContentSecurityPolicyHandler
continue;
}
if (!isset($headers[$header][$type])) {
if (isset($headers[$header]['default-src'])) {
$headers[$header][$type] = $headers[$header]['default-src'];
} else {
// If there is no script-src/style-src and no default-src, no additional rules required.
if (null === $fallback = $this->getDirectiveFallback($directives, $type)) {
continue;
}
$headers[$header][$type] = $fallback;
}
$ruleIsSet = true;
if (!\in_array('\'unsafe-inline\'', $headers[$header][$type], true)) {
@ -199,9 +198,7 @@ class ContentSecurityPolicyHandler
{
if (isset($directivesSet[$type])) {
$directives = $directivesSet[$type];
} elseif (isset($directivesSet['default-src'])) {
$directives = $directivesSet['default-src'];
} else {
} elseif (null === $directives = $this->getDirectiveFallback($directivesSet, $type)) {
return false;
}
@ -225,6 +222,16 @@ class ContentSecurityPolicyHandler
return false;
}
private function getDirectiveFallback(array $directiveSet, $type)
{
if (\in_array($type, ['script-src-elem', 'style-src-elem'], true) || !isset($directiveSet['default-src'])) {
// Let the browser fallback on it's own
return null;
}
return $directiveSet['default-src'];
}
/**
* Retrieves the Content-Security-Policy headers (either X-Content-Security-Policy or Content-Security-Policy) from
* a response.

View File

@ -131,7 +131,14 @@ 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\'; 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],
['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],
],
[
$nonce,
['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\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'']),
['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\' \'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\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null],
],
[
$nonce,

View File

@ -690,7 +690,7 @@ class YamlFileLoader extends FileLoader
try {
$configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
} catch (ParseException $e) {
throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: '.$e->getMessage(), $file), 0, $e);
throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML', $file).': '.$e->getMessage(), 0, $e);
}
return $this->validate($configuration, $file);

View File

@ -454,28 +454,19 @@ class Filesystem
$startPath = str_replace('\\', '/', $startPath);
}
$stripDriveLetter = function ($path) {
if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) {
return substr($path, 2);
}
return $path;
$splitDriveLetter = function ($path) {
return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
? [substr($path, 2), strtoupper($path[0])]
: [$path, null];
};
$endPath = $stripDriveLetter($endPath);
$startPath = $stripDriveLetter($startPath);
// Split the paths into arrays
$startPathArr = explode('/', trim($startPath, '/'));
$endPathArr = explode('/', trim($endPath, '/'));
$normalizePathArray = function ($pathSegments) {
$splitPath = function ($path) {
$result = [];
foreach ($pathSegments as $segment) {
foreach (explode('/', trim($path, '/')) as $segment) {
if ('..' === $segment) {
array_pop($result);
} elseif ('.' !== $segment) {
} elseif ('.' !== $segment && '' !== $segment) {
$result[] = $segment;
}
}
@ -483,8 +474,16 @@ class Filesystem
return $result;
};
$startPathArr = $normalizePathArray($startPathArr);
$endPathArr = $normalizePathArray($endPathArr);
list($endPath, $endDriveLetter) = $splitDriveLetter($endPath);
list($startPath, $startDriveLetter) = $splitDriveLetter($startPath);
$startPathArr = $splitPath($startPath);
$endPathArr = $splitPath($endPath);
if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
// End path is on another drive, so no relative path exists
return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
}
// Find for which directory the common path stops
$index = 0;

View File

@ -1107,10 +1107,14 @@ class FilesystemTest extends FilesystemTestCase
['/../aa/bb/cc', '/aa/dd/..', 'bb/cc/'],
['/../../aa/../bb/cc', '/aa/dd/..', '../bb/cc/'],
['C:/aa/bb/cc', 'C:/aa/dd/..', 'bb/cc/'],
['C:/aa/bb/cc', 'c:/aa/dd/..', 'bb/cc/'],
['c:/aa/../bb/cc', 'c:/aa/dd/..', '../bb/cc/'],
['C:/aa/bb/../../cc', 'C:/aa/../dd/..', 'cc/'],
['C:/../aa/bb/cc', 'C:/aa/dd/..', 'bb/cc/'],
['C:/../../aa/../bb/cc', 'C:/aa/dd/..', '../bb/cc/'],
['D:/', 'C:/aa/../bb/cc', 'D:/'],
['D:/aa/bb', 'C:/aa', 'D:/aa/bb/'],
['D:/../../aa/../bb/cc', 'C:/aa/dd/..', 'D:/bb/cc/'],
];
if ('\\' === \DIRECTORY_SEPARATOR) {

View File

@ -959,7 +959,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
*
* @return FormInterface The child form
*
* @throws \OutOfBoundsException if the named child does not exist
* @throws OutOfBoundsException if the named child does not exist
*/
public function offsetGet($name)
{

View File

@ -62,7 +62,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
*
* @return self
*
* @throws \OutOfBoundsException if the named child does not exist
* @throws Exception\OutOfBoundsException if the named child does not exist
*/
public function get($name);

View File

@ -294,16 +294,16 @@ class InflectorTest extends TestCase
/**
* @dataProvider singularizeProvider
*/
public function testSingularize($plural, $singular)
public function testSingularize($plural, $expectedSingular)
{
$single = Inflector::singularize($plural);
if (\is_string($singular) && \is_array($single)) {
$this->fail("--- Expected\n`string`: ".$singular."\n+++ Actual\n`array`: ".implode(', ', $single));
} elseif (\is_array($singular) && \is_string($single)) {
$this->fail("--- Expected\n`array`: ".implode(', ', $singular)."\n+++ Actual\n`string`: ".$single);
$singular = Inflector::singularize($plural);
if (\is_string($expectedSingular) && \is_array($singular)) {
$this->fail("--- Expected\n`string`: ".$expectedSingular."\n+++ Actual\n`array`: ".implode(', ', $singular));
} elseif (\is_array($expectedSingular) && \is_string($singular)) {
$this->fail("--- Expected\n`array`: ".implode(', ', $expectedSingular)."\n+++ Actual\n`string`: ".$singular);
}
$this->assertEquals($singular, $single);
$this->assertEquals($expectedSingular, $singular);
}
/**

View File

@ -62,7 +62,7 @@ class YamlFileLoader extends FileLoader
try {
$parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML', $path).': '.$e->getMessage(), 0, $e);
}
$collection = new RouteCollection();

View File

@ -42,7 +42,7 @@ class YamlFileLoader extends FileLoader
try {
$messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new InvalidResourceException(sprintf('Error parsing YAML, invalid file "%s".', $resource), 0, $e);
throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML', $resource).': '.$e->getMessage(), 0, $e);
}
if (null !== $messages && !\is_array($messages)) {

View File

@ -80,6 +80,9 @@ class EmailValidator extends ConstraintValidator
}
$value = (string) $value;
if ('' === $value) {
return;
}
if (null !== $constraint->normalizer) {
$value = ($constraint->normalizer)($value);

View File

@ -114,7 +114,7 @@ class YamlFileLoader extends FileLoader
try {
$classes = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML', $path).': '.$e->getMessage(), 0, $e);
}
// empty file

View File

@ -352,7 +352,7 @@
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Ova vrednost bi trebala biti pozitivna ili nula.</target>
<target>Ova vrednost bi trebala biti negativna ili nula.</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>

View File

@ -60,6 +60,13 @@ class EmailValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
public function testObjectEmptyStringIsValid()
{
$this->validator->validate(new EmptyEmailObject(), new Email());
$this->assertNoViolation();
}
public function testExpectsStringCompatibleType()
{
$this->expectException('Symfony\Component\Validator\Exception\UnexpectedValueException');
@ -411,3 +418,11 @@ class EmailValidatorTest extends ConstraintValidatorTestCase
];
}
}
class EmptyEmailObject
{
public function __toString()
{
return '';
}
}

View File

@ -53,6 +53,11 @@ class Entity extends EntityParent implements EntityInterfaceB
$this->internal = $internal;
}
public function getFirstName()
{
return $this->firstName;
}
public function getInternal()
{
return $this->internal.' from getter';
@ -141,4 +146,9 @@ class Entity extends EntityParent implements EntityInterfaceB
{
$this->childB = $childB;
}
public function getReference()
{
return $this->reference;
}
}

View File

@ -32,6 +32,8 @@ abstract class AbstractValidatorTest extends TestCase
const REFERENCE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Reference';
const LAZY_PROPERTY = 'Symfony\Component\Validator\Validator\LazyProperty';
/**
* @var FakeMetadataFactory
*/
@ -54,6 +56,7 @@ abstract class AbstractValidatorTest extends TestCase
$this->referenceMetadata = new ClassMetadata(self::REFERENCE_CLASS);
$this->metadataFactory->addMetadata($this->metadata);
$this->metadataFactory->addMetadata($this->referenceMetadata);
$this->metadataFactory->addMetadata(new ClassMetadata(self::LAZY_PROPERTY));
}
protected function tearDown(): void
@ -510,7 +513,10 @@ abstract class AbstractValidatorTest extends TestCase
$this->validate($entity);
}
public function testArrayReference()
/**
* @dataProvider getConstraintMethods
*/
public function testArrayReference($constraintMethod)
{
$entity = new Entity();
$entity->reference = ['key' => new Reference()];
@ -528,7 +534,7 @@ abstract class AbstractValidatorTest extends TestCase
$context->addViolation('Message %param%', ['%param%' => 'value']);
};
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->metadata->$constraintMethod('reference', new Valid());
$this->referenceMetadata->addConstraint(new Callback([
'callback' => $callback,
'groups' => 'Group',
@ -548,8 +554,10 @@ abstract class AbstractValidatorTest extends TestCase
$this->assertNull($violations[0]->getCode());
}
// https://github.com/symfony/symfony/issues/6246
public function testRecursiveArrayReference()
/**
* @dataProvider getConstraintMethods
*/
public function testRecursiveArrayReference($constraintMethod)
{
$entity = new Entity();
$entity->reference = [2 => ['key' => new Reference()]];
@ -567,7 +575,7 @@ abstract class AbstractValidatorTest extends TestCase
$context->addViolation('Message %param%', ['%param%' => 'value']);
};
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->metadata->$constraintMethod('reference', new Valid());
$this->referenceMetadata->addConstraint(new Callback([
'callback' => $callback,
'groups' => 'Group',
@ -611,7 +619,10 @@ abstract class AbstractValidatorTest extends TestCase
$this->assertCount(0, $violations);
}
public function testArrayTraversalCannotBeDisabled()
/**
* @dataProvider getConstraintMethods
*/
public function testArrayTraversalCannotBeDisabled($constraintMethod)
{
$entity = new Entity();
$entity->reference = ['key' => new Reference()];
@ -620,7 +631,7 @@ abstract class AbstractValidatorTest extends TestCase
$context->addViolation('Message %param%', ['%param%' => 'value']);
};
$this->metadata->addPropertyConstraint('reference', new Valid([
$this->metadata->$constraintMethod('reference', new Valid([
'traverse' => false,
]));
$this->referenceMetadata->addConstraint(new Callback($callback));
@ -631,7 +642,10 @@ abstract class AbstractValidatorTest extends TestCase
$this->assertCount(1, $violations);
}
public function testRecursiveArrayTraversalCannotBeDisabled()
/**
* @dataProvider getConstraintMethods
*/
public function testRecursiveArrayTraversalCannotBeDisabled($constraintMethod)
{
$entity = new Entity();
$entity->reference = [2 => ['key' => new Reference()]];
@ -640,9 +654,10 @@ abstract class AbstractValidatorTest extends TestCase
$context->addViolation('Message %param%', ['%param%' => 'value']);
};
$this->metadata->addPropertyConstraint('reference', new Valid([
$this->metadata->$constraintMethod('reference', new Valid([
'traverse' => false,
]));
$this->referenceMetadata->addConstraint(new Callback($callback));
$violations = $this->validate($entity);
@ -651,12 +666,15 @@ abstract class AbstractValidatorTest extends TestCase
$this->assertCount(1, $violations);
}
public function testIgnoreScalarsDuringArrayTraversal()
/**
* @dataProvider getConstraintMethods
*/
public function testIgnoreScalarsDuringArrayTraversal($constraintMethod)
{
$entity = new Entity();
$entity->reference = ['string', 1234];
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->metadata->$constraintMethod('reference', new Valid());
$violations = $this->validate($entity);
@ -664,12 +682,15 @@ abstract class AbstractValidatorTest extends TestCase
$this->assertCount(0, $violations);
}
public function testIgnoreNullDuringArrayTraversal()
/**
* @dataProvider getConstraintMethods
*/
public function testIgnoreNullDuringArrayTraversal($constraintMethod)
{
$entity = new Entity();
$entity->reference = [null];
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->metadata->$constraintMethod('reference', new Valid());
$violations = $this->validate($entity);
@ -1218,6 +1239,14 @@ abstract class AbstractValidatorTest extends TestCase
}
}
public function getConstraintMethods()
{
return [
['addPropertyConstraint'],
['addGetterConstraint'],
];
}
public function getTestReplaceDefaultGroup()
{
return [

View File

@ -634,6 +634,10 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
// See validateClassNode()
$cascadedGroups = null !== $cascadedGroups && \count($cascadedGroups) > 0 ? $cascadedGroups : $groups;
if ($value instanceof LazyProperty) {
$value = $value->getPropertyValue();
}
if (\is_array($value)) {
// Arrays are always traversed, independent of the specified
// traversal strategy

View File

@ -618,8 +618,14 @@ class Parser
}
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
$isItComment = $this->isCurrentLineComment();
while ($this->moveToNextLine()) {
if ($isItComment && !$isItUnindentedCollection) {
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
$isItComment = $this->isCurrentLineComment();
}
$indent = $this->getCurrentLineIndentation();
if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {

View File

@ -74,3 +74,17 @@ yaml: |
'foo #': baz
php: |
['foo #' => 'baz']
---
test: Comment before first item in unindented collection
brief: >
Comment directly before unindented collection is allowed
yaml: |
collection1:
# comment
- a
- b
collection2:
- a
- b
php: |
['collection1' => ['a', 'b'], 'collection2' => ['a', 'b']]