From 3a661cf7be690af8efe144749c9c6b7143498177 Mon Sep 17 00:00:00 2001 From: Lauri Eskola Date: Thu, 17 Aug 2023 23:33:59 +0300 Subject: [PATCH] Issue #3379102 by phenaproxima, Wim Leers, borisson_: Add validation constraint to type: label + type: text: disallow control characters --- core/config/schema/core.data_types.schema.yml | 22 ++++-- .../tests/src/Kernel/BlockValidationTest.php | 2 +- .../config/schema/views.field.schema.yml | 9 +++ .../Config/ConfigEntityValidationTestBase.php | 2 +- .../Config/SimpleConfigValidationTest.php | 77 +++++++++++++++++++ 5 files changed, 104 insertions(+), 8 deletions(-) diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index ab50fa57fd9..56cc1506b41 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -64,13 +64,11 @@ label: translatable: true constraints: Regex: - # Forbid any kind of line ending: - # - Windows: `\r\n` - # - old macOS: `\r` - # - *nix: `\n` - pattern: '/(\r\n|\r|\n)/' + # Forbid any kind of control character. + # @see https://stackoverflow.com/a/66587087 + pattern: '/([^\PC])/u' match: false - message: 'Labels are not allowed to span multiple lines.' + message: 'Labels are not allowed to span multiple lines or contain control characters.' required_label: type: label @@ -93,6 +91,18 @@ text: type: string label: 'Text' translatable: true + constraints: + Regex: + # Disallow all control characters except for tabs (ASCII 9, 0x09) as well + # as carriage returns (ASCII 13, 0x0D) and line feeds (ASCII 10, 0x0A), + # which are used for line endings: + # - Windows: `\r\n` + # - old macOS: `\r` + # - *nix: `\n` + # @see https://stackoverflow.com/a/66587087 + pattern: '/([^\PC\x09\x0a\x0d])/u' + match: false + message: 'Text is not allowed to contain control characters, only visible characters.' # A UUID. uuid: diff --git a/core/modules/block/tests/src/Kernel/BlockValidationTest.php b/core/modules/block/tests/src/Kernel/BlockValidationTest.php index f9acd355bdd..81a792d8991 100644 --- a/core/modules/block/tests/src/Kernel/BlockValidationTest.php +++ b/core/modules/block/tests/src/Kernel/BlockValidationTest.php @@ -86,7 +86,7 @@ class BlockValidationTest extends ConfigEntityValidationTestBase { // key, it is impossible for the generic ::testLabelValidation() // implementation in the base class to know at which property to expect a // validation error. Hence it is hardcoded in this case. - $this->assertValidationErrors(['settings.label' => "Labels are not allowed to span multiple lines."]); + $this->assertValidationErrors(['settings.label' => "Labels are not allowed to span multiple lines or contain control characters."]); } } diff --git a/core/modules/views/config/schema/views.field.schema.yml b/core/modules/views/config/schema/views.field.schema.yml index d1c3ce67fa8..4a936563b31 100644 --- a/core/modules/views/config/schema/views.field.schema.yml +++ b/core/modules/views/config/schema/views.field.schema.yml @@ -119,6 +119,15 @@ views.field.numeric: format_plural_string: type: plural_label label: 'Plural variants' + constraints: + Regex: + # Normally, labels cannot contain invisible control characters. In this particular + # case, an invisible character (ASCII 3, 0x03) is used to encode translation + # information, so carve out an exception for that only. + # @see \Drupal\views\Plugin\views\field\NumericField + pattern: '/([^\PC\x03])/u' + match: false + message: 'Labels are not allowed to span multiple lines or contain control characters.' prefix: type: label label: 'Prefix' diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php index 1f3c8928f8f..45802a579c1 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php @@ -318,7 +318,7 @@ abstract class ConfigEntityValidationTestBase extends KernelTestBase { } static::setLabel($this->entity, "Multi\nLine"); - $this->assertValidationErrors([$this->entity->getEntityType()->getKey('label') => "Labels are not allowed to span multiple lines."]); + $this->assertValidationErrors([$this->entity->getEntityType()->getKey('label') => "Labels are not allowed to span multiple lines or contain control characters."]); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Config/SimpleConfigValidationTest.php b/core/tests/Drupal/KernelTests/Core/Config/SimpleConfigValidationTest.php index 74060fb35bf..633841a7ee9 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/SimpleConfigValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Config/SimpleConfigValidationTest.php @@ -72,4 +72,81 @@ class SimpleConfigValidationTest extends KernelTestBase { $this->assertSame("'invalid_key' is not a supported key.", (string) $violations[0]->getMessage()); } + /** + * Data provider for ::testSpecialCharacters(). + * + * @return array[] + * The test cases. + */ + public function providerSpecialCharacters(): array { + $data = []; + + for ($code_point = 0; $code_point < 32; $code_point++) { + $data["label $code_point"] = [ + 'system.site', + 'name', + mb_chr($code_point), + 'Labels are not allowed to span multiple lines or contain control characters.', + ]; + $data["text $code_point"] = [ + 'system.maintenance', + 'message', + mb_chr($code_point), + 'Text is not allowed to contain control characters, only visible characters.', + ]; + } + // Line feeds (ASCII 10) and carriage returns (ASCII 13) are used to create + // new lines, so they are allowed in text data, along with tabs (ASCII 9). + $data['text 9'][3] = $data['text 10'][3] = $data['text 13'][3] = NULL; + + // Ensure emoji are allowed. + $data['emoji in label'] = [ + 'system.site', + 'name', + '😎', + NULL, + ]; + $data['emoji in text'] = [ + 'system.maintenance', + 'message', + '🤓', + NULL, + ]; + + return $data; + } + + /** + * Tests that special characters are not allowed in labels or text data. + * + * @param string $config_name + * The name of the simple config to test with. + * @param string $property + * The config property in which to embed a control character. + * @param string $character + * A special character to embed. + * @param string|null $expected_error_message + * The expected validation error message, if any. + * + * @dataProvider providerSpecialCharacters + */ + public function testSpecialCharacters(string $config_name, string $property, string $character, ?string $expected_error_message): void { + $config = $this->config($config_name) + ->set($property, "This has a special character: $character"); + + $violations = $this->container->get('config.typed') + ->createFromNameAndData($config->getName(), $config->get()) + ->validate(); + + if ($expected_error_message === NULL) { + $this->assertCount(0, $violations); + } + else { + $code_point = mb_ord($character); + $this->assertCount(1, $violations, "Character $code_point did not raise a constraint violation."); + $this->assertSame($property, $violations[0]->getPropertyPath()); + $this->assertSame($expected_error_message, (string) $violations[0]->getMessage()); + } + } + }