Issue #2560863 by alexpott, stefan.r, lauriii: #options for radios and checkboxes uses SafeMarkup::checkPlain() to escape - use Html::escape() instead
parent
53b28208bb
commit
cc574ff23c
|
@ -27,7 +27,7 @@ interface SelectionInterface extends PluginFormInterface {
|
|||
*
|
||||
* @return array
|
||||
* A nested array of entities, the first level is keyed by the
|
||||
* entity bundle, which contains an array of entity labels (safe HTML),
|
||||
* entity bundle, which contains an array of entity labels (escaped),
|
||||
* keyed by the entity ID.
|
||||
*/
|
||||
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Database\Query\AlterableInterface;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
|
@ -235,7 +235,7 @@ class SelectionBase extends PluginBase implements SelectionInterface, ContainerF
|
|||
$entities = entity_load_multiple($target_type, $result);
|
||||
foreach ($entities as $entity_id => $entity) {
|
||||
$bundle = $entity->bundle();
|
||||
$options[$bundle][$entity_id] = SafeMarkup::checkPlain($entity->label());
|
||||
$options[$bundle][$entity_id] = Html::escape($entity->label());
|
||||
}
|
||||
|
||||
return $options;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
namespace Drupal\entity_reference;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
|
||||
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
|
||||
|
@ -80,7 +80,9 @@ class ConfigurableEntityReferenceItem extends EntityReferenceItem implements Opt
|
|||
|
||||
$return = array();
|
||||
foreach ($options as $bundle => $entity_ids) {
|
||||
$bundle_label = SafeMarkup::checkPlain($bundles[$bundle]['label']);
|
||||
// The label does not need sanitizing since it is used as an optgroup
|
||||
// which is only supported by select elements and auto-escaped.
|
||||
$bundle_label = $bundles[$bundle]['label'];
|
||||
$return[$bundle_label] = $entity_ids;
|
||||
}
|
||||
|
||||
|
@ -135,11 +137,11 @@ class ConfigurableEntityReferenceItem extends EntityReferenceItem implements Opt
|
|||
// entity type specific plugins (e.g., 'default:node', 'default:user',
|
||||
// etc.).
|
||||
if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
|
||||
$handlers_options[$selection_group_id] = SafeMarkup::checkPlain($selection_plugins[$selection_group_id][$selection_group_id]['label']);
|
||||
$handlers_options[$selection_group_id] = Html::escape($selection_plugins[$selection_group_id][$selection_group_id]['label']);
|
||||
}
|
||||
elseif (array_key_exists($selection_group_id . ':' . $this->getSetting('target_type'), $selection_plugins[$selection_group_id])) {
|
||||
$selection_group_plugin = $selection_group_id . ':' . $this->getSetting('target_type');
|
||||
$handlers_options[$selection_group_plugin] = SafeMarkup::checkPlain($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
|
||||
$handlers_options[$selection_group_plugin] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\entity_reference\Tests\EntityReferenceXSSTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\entity_reference\Tests;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests possible XSS security issues in entity references.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceXSSTest extends WebTestBase {
|
||||
|
||||
use EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['node', 'entity_reference'];
|
||||
|
||||
/**
|
||||
* Tests markup is escaped in the entity reference select and label formatter.
|
||||
*/
|
||||
public function testEntityReferenceXSS() {
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
|
||||
// Create a node with markup in the title.
|
||||
$node_type_one = $this->drupalCreateContentType();
|
||||
$node = [
|
||||
'type' => $node_type_one->id(),
|
||||
'title' => '<em>I am kitten</em>',
|
||||
];
|
||||
$referenced_node = $this->drupalCreateNode($node);
|
||||
|
||||
$node_type_two = $this->drupalCreateContentType(['name' => '<em>bundle with markup</em>']);
|
||||
$this->drupalCreateNode([
|
||||
'type' => $node_type_two->id(),
|
||||
'title' => 'My bundle has markup',
|
||||
]);
|
||||
|
||||
$this->createEntityReferenceField('node', 'article', 'entity_reference_test', 'Entity Reference test', 'node', 'default', ['target_bundles' => [$node_type_one->id(), $node_type_two->id()]]);
|
||||
|
||||
EntityFormDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'options_select'])
|
||||
->save();
|
||||
EntityViewDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'entity_reference_label'])
|
||||
->save();
|
||||
|
||||
// Create a node and reference the node with markup in the title.
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertEscaped($referenced_node->getTitle());
|
||||
$this->assertEscaped($node_type_two->label());
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomString(),
|
||||
'entity_reference_test' => $referenced_node->id()
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save and publish');
|
||||
$this->assertEscaped($referenced_node->getTitle());
|
||||
|
||||
// Test the options_buttons type.
|
||||
EntityFormDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'options_buttons'])
|
||||
->save();
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertEscaped($referenced_node->getTitle());
|
||||
// options_buttons does not support optgroups.
|
||||
$this->assertNoText('bundle with markup');
|
||||
}
|
||||
|
||||
}
|
|
@ -79,7 +79,7 @@ abstract class FilterFormatFormBase extends EntityForm {
|
|||
$form['roles'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Roles'),
|
||||
'#options' => array_map('\Drupal\Component\Utility\SafeMarkup::checkPlain', user_role_names()),
|
||||
'#options' => array_map('\Drupal\Component\Utility\Html::escape', user_role_names()),
|
||||
'#disabled' => $is_fallback,
|
||||
'#weight' => -10,
|
||||
);
|
||||
|
|
|
@ -554,7 +554,7 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
|
|||
);
|
||||
|
||||
// Add node types.
|
||||
$types = array_map(array('\Drupal\Component\Utility\SafeMarkup', 'checkPlain'), node_type_get_names());
|
||||
$types = array_map(array('\Drupal\Component\Utility\Html', 'escape'), node_type_get_names());
|
||||
$form['advanced']['types-fieldset'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Types'),
|
||||
|
|
|
@ -57,8 +57,8 @@ class EntityAutocompleteTest extends EntityUnitTestBase {
|
|||
$entity_2 = entity_create($this->entityType, array('name' => '10/17/2011'));
|
||||
$entity_2->save();
|
||||
|
||||
// Add another entity that has both a comma and a slash character.
|
||||
$entity_3 = entity_create($this->entityType, array('name' => 'label with, and / test'));
|
||||
// Add another entity that has both a comma, a slash and markup.
|
||||
$entity_3 = entity_create($this->entityType, array('name' => 'label with, and / <em>test</em>'));
|
||||
$entity_3->save();
|
||||
|
||||
// Try to autocomplete a entity label that matches both entities.
|
||||
|
@ -84,8 +84,8 @@ class EntityAutocompleteTest extends EntityUnitTestBase {
|
|||
$data = $this->getAutocompleteResult($input);
|
||||
$this->assertIdentical($data[0]['label'], Html::escape($entity_2->name->value), 'Autocomplete returned the second matching entity');
|
||||
|
||||
// Try to autocomplete a entity label with both a comma and a slash.
|
||||
$input = '"label with, and / t';
|
||||
// Try to autocomplete a entity label with both a comma, a slash and markup.
|
||||
$input = '"label with, and / <em>';
|
||||
$data = $this->getAutocompleteResult($input);
|
||||
$n = $entity_3->name->value . ' (3)';
|
||||
// Entity labels containing commas or quotes must be wrapped in quotes.
|
||||
|
|
|
@ -62,6 +62,12 @@ class ElementTest extends WebTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
// Verify that the choices are admin filtered as expected.
|
||||
$this->assertRaw("<em>Special Char</em>alert('checkboxes');");
|
||||
$this->assertRaw("<em>Special Char</em>alert('radios');");
|
||||
$this->assertRaw('<em>Bar - checkboxes</em>');
|
||||
$this->assertRaw('<em>Bar - radios</em>');
|
||||
|
||||
// Enable customized option sub-elements.
|
||||
$this->drupalGet('form-test/checkboxes-radios/customize');
|
||||
|
||||
|
|
|
@ -363,6 +363,10 @@ class FormTest extends WebTestBase {
|
|||
$form = \Drupal::formBuilder()->getForm('Drupal\form_test\Form\FormTestSelectForm');
|
||||
$this->drupalGet('form-test/select');
|
||||
|
||||
// Verify that the options are escaped as expected.
|
||||
$this->assertEscaped('<strong>four</strong>');
|
||||
$this->assertNoRaw('<strong>four</strong>');
|
||||
|
||||
// Posting without any values should throw validation errors.
|
||||
$this->drupalPostForm(NULL, array(), 'Submit');
|
||||
$no_errors = array(
|
||||
|
|
|
@ -36,8 +36,8 @@ class FormTestCheckboxesRadiosForm extends FormBase {
|
|||
0 => 'Zero',
|
||||
'foo' => 'Foo',
|
||||
1 => 'One',
|
||||
'bar' => 'Bar',
|
||||
'>' => 'Special Char',
|
||||
'bar' => $this->t('<em>Bar - checkboxes</em>'),
|
||||
'>' => "<em>Special Char</em><script>alert('checkboxes');</script>",
|
||||
),
|
||||
);
|
||||
if ($customize) {
|
||||
|
@ -60,8 +60,8 @@ class FormTestCheckboxesRadiosForm extends FormBase {
|
|||
0 => 'Zero',
|
||||
'foo' => 'Foo',
|
||||
1 => 'One',
|
||||
'bar' => 'Bar',
|
||||
'>' => 'Special Char',
|
||||
'bar' => '<em>Bar - radios</em>',
|
||||
'>' => "<em>Special Char</em><script>alert('radios');</script>",
|
||||
),
|
||||
);
|
||||
if ($customize) {
|
||||
|
|
|
@ -29,7 +29,7 @@ class FormTestSelectForm extends FormBase {
|
|||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$base = array(
|
||||
'#type' => 'select',
|
||||
'#options' => array('one' => 'one', 'two' => 'two', 'three' => 'three'),
|
||||
'#options' => array('one' => 'one', 'two' => 'two', 'three' => 'three', 'four' => '<strong>four</strong>'),
|
||||
);
|
||||
|
||||
$form['select'] = $base + array(
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
namespace Drupal\taxonomy\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
@ -73,7 +73,7 @@ class TermSelection extends SelectionBase {
|
|||
if ($vocabulary = Vocabulary::load($bundle)) {
|
||||
if ($terms = $this->entityManager->getStorage('taxonomy_term')->loadTree($vocabulary->id(), 0, NULL, TRUE)) {
|
||||
foreach ($terms as $term) {
|
||||
$options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . SafeMarkup::checkPlain($term->getName());
|
||||
$options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . Html::escape($term->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ class TaxonomyIndexTid extends ManyToOne {
|
|||
if ($tree) {
|
||||
foreach ($tree as $term) {
|
||||
$choice = new \stdClass();
|
||||
$choice->option = array($term->id() => str_repeat('-', $term->depth) . SafeMarkup::checkPlain(\Drupal::entityManager()->getTranslationFromContext($term)->label()));
|
||||
$choice->option = array($term->id() => str_repeat('-', $term->depth) . \Drupal::entityManager()->getTranslationFromContext($term)->label());
|
||||
$options[] = $choice;
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ class TaxonomyIndexTid extends ManyToOne {
|
|||
}
|
||||
$terms = Term::loadMultiple($query->execute());
|
||||
foreach ($terms as $term) {
|
||||
$options[$term->id()] = SafeMarkup::checkPlain(\Drupal::entityManager()->getTranslationFromContext($term)->label());
|
||||
$options[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ abstract class AccountForm extends ContentEntityForm {
|
|||
'#access' => $admin,
|
||||
);
|
||||
|
||||
$roles = array_map(array('\Drupal\Component\Utility\SafeMarkup', 'checkPlain'), user_role_names(TRUE));
|
||||
$roles = array_map(array('\Drupal\Component\Utility\Html', 'escape'), user_role_names(TRUE));
|
||||
|
||||
$form['account']['roles'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
|
|
|
@ -115,7 +115,7 @@ class Role extends AccessPluginBase implements CacheablePluginInterface {
|
|||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Role'),
|
||||
'#default_value' => $this->options['role'],
|
||||
'#options' => array_map('\Drupal\Component\Utility\SafeMarkup::checkPlain', user_role_names()),
|
||||
'#options' => array_map('\Drupal\Component\Utility\Html::escape', user_role_names()),
|
||||
'#description' => $this->t('Only the checked roles will be able to access this display.'),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class User extends Entity {
|
|||
$form['roles'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Restrict to the selected roles'),
|
||||
'#options' => array_map(array('\Drupal\Component\Utility\SafeMarkup', 'checkPlain'), user_role_names(TRUE)),
|
||||
'#options' => array_map(array('\Drupal\Component\Utility\Html', 'escape'), user_role_names(TRUE)),
|
||||
'#default_value' => $this->options['roles'],
|
||||
'#description' => $this->t('If no roles are selected, users from any role will be allowed.'),
|
||||
'#states' => array(
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
namespace Drupal\user\Plugin\views\filter;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\user\PermissionHandlerInterface;
|
||||
use Drupal\views\Plugin\views\filter\ManyToOne;
|
||||
|
@ -76,7 +76,7 @@ class Permissions extends ManyToOne {
|
|||
foreach ($permissions as $perm => $perm_item) {
|
||||
$provider = $perm_item['provider'];
|
||||
$display_name = $this->moduleHandler->getName($provider);
|
||||
$this->valueOptions[$display_name][$perm] = SafeMarkup::checkPlain(strip_tags($perm_item['title']));
|
||||
$this->valueOptions[$display_name][$perm] = Html::escape(strip_tags($perm_item['title']));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
namespace Drupal\views\Plugin\views\exposed_form;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Form\ViewsExposedForm;
|
||||
|
@ -211,7 +211,7 @@ abstract class ExposedFormPluginBase extends PluginBase implements CacheablePlug
|
|||
$exposed_sorts = array();
|
||||
foreach ($this->view->sort as $id => $handler) {
|
||||
if ($handler->canExpose() && $handler->isExposed()) {
|
||||
$exposed_sorts[$id] = SafeMarkup::checkPlain($handler->options['expose']['label']);
|
||||
$exposed_sorts[$id] = Html::escape($handler->options['expose']['label']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -594,7 +594,7 @@ abstract class FilterPluginBase extends HandlerBase implements CacheablePluginIn
|
|||
'#default_value' => $this->options['expose']['remember'],
|
||||
);
|
||||
|
||||
$role_options = array_map('\Drupal\Component\Utility\SafeMarkup::checkPlain', user_role_names());
|
||||
$role_options = array_map('\Drupal\Component\Utility\Html::escape', user_role_names());
|
||||
$form['expose']['remember_roles'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('User roles'),
|
||||
|
|
Loading…
Reference in New Issue