From e513467c52be9682cfc50ba00bf04eccd0599d24 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Mon, 31 Aug 2015 10:59:15 +0100 Subject: [PATCH] Issue #2560137 by dawehner, pwolanin, klausi, tsphethean, sun: Posting an array as value of a form element is allowed even when a string is expected (and bypasses #maxlength constraints) - first step: text fields --- .../Core/Render/Element/MachineName.php | 5 ++ .../Drupal/Core/Render/Element/Password.php | 13 ++++++ .../Core/Render/Element/PasswordConfirm.php | 15 +++++- .../Drupal/Core/Render/Element/Textarea.php | 12 +++++ .../Drupal/Core/Render/Element/Textfield.php | 8 +++- core/lib/Drupal/Core/Render/Element/Token.php | 7 ++- .../Core/Render/Element/MachineNameTest.php | 45 ++++++++++++++++++ .../Render/Element/PasswordConfirmTest.php | 45 ++++++++++++++++++ .../Core/Render/Element/PasswordTest.php | 45 ++++++++++++++++++ .../Core/Render/Element/TextareaTest.php | 45 ++++++++++++++++++ .../Core/Render/Element/TextfieldTest.php | 46 +++++++++++++++++++ .../Tests/Core/Render/Element/TokenTest.php | 45 ++++++++++++++++++ 12 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/PasswordConfirmTest.php create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/PasswordTest.php create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/TextareaTest.php create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/TextfieldTest.php create mode 100644 core/tests/Drupal/Tests/Core/Render/Element/TokenTest.php diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php index f351ddce25b..66c85c76df5 100644 --- a/core/lib/Drupal/Core/Render/Element/MachineName.php +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php @@ -104,6 +104,11 @@ class MachineName extends Textfield { * {@inheritdoc} */ public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input !== FALSE && $input !== NULL) { + // This should be a string, but allow other scalars since they might be + // valid input in programmatic form submissions. + return is_scalar($input) ? (string) $input : ''; + } return NULL; } diff --git a/core/lib/Drupal/Core/Render/Element/Password.php b/core/lib/Drupal/Core/Render/Element/Password.php index 308d53bedf0..bd54c98af17 100644 --- a/core/lib/Drupal/Core/Render/Element/Password.php +++ b/core/lib/Drupal/Core/Render/Element/Password.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Render\Element; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; /** @@ -68,4 +69,16 @@ class Password extends FormElement { return $element; } + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input !== FALSE && $input !== NULL) { + // This should be a string, but allow other scalars since they might be + // valid input in programmatic form submissions. + return is_scalar($input) ? (string) $input : ''; + } + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php index 7f3efa34e6d..026cb9a8d0f 100644 --- a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php +++ b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php @@ -50,9 +50,20 @@ class PasswordConfirm extends FormElement { */ public static function valueCallback(&$element, $input, FormStateInterface $form_state) { if ($input === FALSE) { - $element += array('#default_value' => array()); - return $element['#default_value'] + array('pass1' => '', 'pass2' => ''); + $element += ['#default_value' => []]; + return $element['#default_value'] + ['pass1' => '', 'pass2' => '']; } + $value = ['pass1' => '', 'pass2' => '']; + // Throw out all invalid array keys; we only allow pass1 and pass2. + foreach ($value as $allowed_key => $default) { + // These should be strings, but allow other scalars since they might be + // valid input in programmatic form submissions. Any nested array values + // are ignored. + if (isset($input[$allowed_key]) && is_scalar($input[$allowed_key])) { + $value[$allowed_key] = (string) $input[$allowed_key]; + } + } + return $value; } /** diff --git a/core/lib/Drupal/Core/Render/Element/Textarea.php b/core/lib/Drupal/Core/Render/Element/Textarea.php index 60fc5ccd9f5..efeebcda698 100644 --- a/core/lib/Drupal/Core/Render/Element/Textarea.php +++ b/core/lib/Drupal/Core/Render/Element/Textarea.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Render\Element; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; /** @@ -55,4 +56,15 @@ class Textarea extends FormElement { ); } + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input !== FALSE && $input !== NULL) { + // This should be a string, but allow other scalars since they might be + // valid input in programmatic form submissions. + return is_scalar($input) ? (string) $input : ''; + } + return NULL; + } } diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/Textfield.php index 213ca07a812..7348b4f751f 100644 --- a/core/lib/Drupal/Core/Render/Element/Textfield.php +++ b/core/lib/Drupal/Core/Render/Element/Textfield.php @@ -77,10 +77,14 @@ class Textfield extends FormElement { */ public static function valueCallback(&$element, $input, FormStateInterface $form_state) { if ($input !== FALSE && $input !== NULL) { - // Equate $input to the form value to ensure it's marked for - // validation. + // This should be a string, but allow other scalars since they might be + // valid input in programmatic form submissions. + if (!is_scalar($input)) { + $input = ''; + } return str_replace(array("\r", "\n"), '', $input); } + return NULL; } /** diff --git a/core/lib/Drupal/Core/Render/Element/Token.php b/core/lib/Drupal/Core/Render/Element/Token.php index 8b526c254fa..b45bde3e946 100644 --- a/core/lib/Drupal/Core/Render/Element/Token.php +++ b/core/lib/Drupal/Core/Render/Element/Token.php @@ -39,9 +39,12 @@ class Token extends Hidden { * {@inheritdoc} */ public static function valueCallback(&$element, $input, FormStateInterface $form_state) { - if ($input !== FALSE) { - return (string) $input; + if ($input !== FALSE && $input !== NULL) { + // This should be a string, but allow other scalars since they might be + // valid input in programmatic form submissions. + return is_scalar($input) ? (string) $input : ''; } + return NULL; } } diff --git a/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php b/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php new file mode 100644 index 00000000000..7546c27a284 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php @@ -0,0 +1,45 @@ +prophesize(FormStateInterface::class)->reveal(); + $this->assertSame($expected, MachineName::valueCallback($element, $input, $form_state)); + } + + /** + * Data provider for testValueCallback(). + */ + public function providerTestValueCallback() { + $data = []; + $data[] = [NULL, FALSE]; + $data[] = [NULL, NULL]; + $data[] = ['', ['test']]; + $data[] = ['test', 'test']; + $data[] = ['123', 123]; + + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/Element/PasswordConfirmTest.php b/core/tests/Drupal/Tests/Core/Render/Element/PasswordConfirmTest.php new file mode 100644 index 00000000000..7f1e1865b4b --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/PasswordConfirmTest.php @@ -0,0 +1,45 @@ +prophesize(FormStateInterface::class)->reveal(); + $this->assertSame($expected, PasswordConfirm::valueCallback($element, $input, $form_state)); + } + + /** + * Data provider for testValueCallback(). + */ + public function providerTestValueCallback() { + $data = []; + $data[] = [['pass1' => '', 'pass2' => ''], [], NULL]; + $data[] = [['pass1' => '', 'pass2' => ''], ['#default_value' => ['pass2' => 'value']], NULL]; + $data[] = [['pass2' => 'value', 'pass1' => ''], ['#default_value' => ['pass2' => 'value']], FALSE]; + $data[] = [['pass1' => '123456', 'pass2' => 'qwerty'], [], ['pass1' => '123456', 'pass2' => 'qwerty']]; + $data[] = [['pass1' => '123', 'pass2' => '234'], [], ['pass1' => 123, 'pass2' => 234]]; + $data[] = [['pass1' => '', 'pass2' => '234'], [], ['pass1' => ['array'], 'pass2' => 234]]; + + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/Element/PasswordTest.php b/core/tests/Drupal/Tests/Core/Render/Element/PasswordTest.php new file mode 100644 index 00000000000..7bb305352e8 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/PasswordTest.php @@ -0,0 +1,45 @@ +prophesize(FormStateInterface::class)->reveal(); + $this->assertSame($expected, Password::valueCallback($element, $input, $form_state)); + } + + /** + * Data provider for testValueCallback(). + */ + public function providerTestValueCallback() { + $data = []; + $data[] = [NULL, FALSE]; + $data[] = [NULL, NULL]; + $data[] = ['', ['test']]; + $data[] = ['test', 'test']; + $data[] = ['123', 123]; + + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/Element/TextareaTest.php b/core/tests/Drupal/Tests/Core/Render/Element/TextareaTest.php new file mode 100644 index 00000000000..2ebf4199712 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/TextareaTest.php @@ -0,0 +1,45 @@ +prophesize(FormStateInterface::class)->reveal(); + $this->assertSame($expected, Textarea::valueCallback($element, $input, $form_state)); + } + + /** + * Data provider for testValueCallback(). + */ + public function providerTestValueCallback() { + $data = []; + $data[] = [NULL, FALSE]; + $data[] = [NULL, NULL]; + $data[] = ['', ['test']]; + $data[] = ['test', 'test']; + $data[] = ['123', 123]; + + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/Element/TextfieldTest.php b/core/tests/Drupal/Tests/Core/Render/Element/TextfieldTest.php new file mode 100644 index 00000000000..7a1860b94a9 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/TextfieldTest.php @@ -0,0 +1,46 @@ +prophesize(FormStateInterface::class)->reveal(); + $this->assertSame($expected, Textfield::valueCallback($element, $input, $form_state)); + } + + /** + * Data provider for testValueCallback(). + */ + public function providerTestValueCallback() { + $data = []; + $data[] = [NULL, FALSE]; + $data[] = [NULL, NULL]; + $data[] = ['', ['test']]; + $data[] = ['test', 'test']; + $data[] = ['123', 123]; + $data[] = ['testwithnewline', "test\nwith\rnewline"]; + + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Render/Element/TokenTest.php b/core/tests/Drupal/Tests/Core/Render/Element/TokenTest.php new file mode 100644 index 00000000000..bd139662720 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Render/Element/TokenTest.php @@ -0,0 +1,45 @@ +prophesize(FormStateInterface::class)->reveal(); + $this->assertSame($expected, Token::valueCallback($element, $input, $form_state)); + } + + /** + * Data provider for testValueCallback(). + */ + public function providerTestValueCallback() { + $data = []; + $data[] = [NULL, FALSE]; + $data[] = [NULL, NULL]; + $data[] = ['', ['test']]; + $data[] = ['test', 'test']; + $data[] = ['123', 123]; + + return $data; + } + +}