-
Luke Leber authored
Issue #3336223 by Luke.Leber: Visual HTML5 Layout: Pre-emptively strip <script> tags from input sources
Luke Leber authoredIssue #3336223 by Luke.Leber: Visual HTML5 Layout: Pre-emptively strip <script> tags from input sources
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
VisualInlineHtml5DiffLayout.php 6.43 KiB
<?php
namespace Drupal\diff_plus\Plugin\diff\Layout;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Render\Markup;
use Drupal\diff\Controller\PluginRevisionController;
use Drupal\diff\Plugin\diff\Layout\VisualInlineDiffLayout;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides Layout Builder diff layout.
*
* @DiffLayoutBuilder(
* id = "visual_inline_html5",
* label = @Translation("Visual Inline (HTML5)"),
* description = @Translation("HTML-5 compatible visual layout, displays revision comparison using the entity type view mode."),
* )
*/
class VisualInlineHtml5DiffLayout extends VisualInlineDiffLayout {
/**
* An array of "safe" HTML tags to pass to the XSS filter.
*/
protected const HTML5_TAGS = [
'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base',
'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption',
'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del',
'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset',
'figcaption', 'figure', 'footer', 'form', 'head', 'header', 'hgroup',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'html', 'i', 'iframe', 'img',
'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main',
'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript',
'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture',
'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section',
'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary',
'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot',
'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video',
'wbr',
];
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* The user data service.
*
* @var \Drupal\user\UserDataInterface
*/
protected $userData;
/**
* The account switcher service.
*
* @var \Drupal\Core\Session\AccountSwitcherInterface
*/
protected $accountSwitcher;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->htmlDiff->getConfig()->setPurifierEnabled(FALSE);
$instance->configFactory = $container->get('config.factory');
$instance->currentUser = $container->get('current_user');
$instance->userData = $container->get('user.data');
$instance->accountSwitcher = $container->get('account_switcher');
return $instance;
}
/**
* Gets the settings to render the diff with.
*
* @return array
* The settings to render the diff with.
*/
protected function getDiffSettings() {
$settings = $this->configFactory->get('diff_plus.settings')->get();
if ($this->currentUser->hasPermission('personalize diff plus settings')) {
$settings = array_replace_recursive(
$settings,
$this->userData->get('diff_plus', $this->currentUser->id(), 'settings') ?? []
);
}
return $settings;
}
/**
* Preprocesses the provided markup to provide a normalization layer.
*
* @param string $markup
* The markup to normalize.
* @param array $settings
* The diff layout settings.
*
* @return string
* The normalized markup.
*/
protected function preprocessMarkup($markup, array $settings) {
// The diff library has issues with comments, so strip them.
$dom = Html::load($markup);
$xpath = new \DOMXPath($dom);
foreach ($xpath->query('//comment()') as $element) {
$element->parentNode->removeChild($element);
}
// Remove all script tags before Xss::filter can mess things up.
$script_tags = $dom->getElementsByTagName('script');
while ($script_tags->count()) {
$script_tag = $script_tags->item(0);
$script_tag->parentNode->removeChild($script_tag);
}
// Work around a bug in Xss::filter.
if ($settings['visual_html5_preserve_inline_styles']) {
$elements_with_inline_styles = $xpath->query('//*[@style]');
foreach ($elements_with_inline_styles as $element_with_inline_styles) {
$element_with_inline_styles->setAttribute(
'data-diff-plus-style',
$element_with_inline_styles->getAttribute('style')
);
}
}
return Html::serialize($dom);
}
/**
* {@inheritdoc}
*/
public function build(ContentEntityInterface $left_revision, ContentEntityInterface $right_revision, ContentEntityInterface $entity) {
$build = parent::build($left_revision, $right_revision, $entity);
// Fix the view mode selection links.
foreach ($build['controls']['view_mode']['filter']['#links'] as $view_mode => &$link) {
$link['url'] = PluginRevisionController::diffRoute(
$entity,
$left_revision->getRevisionId(),
$right_revision->getRevisionId(),
'visual_inline_html5',
['view_mode' => $view_mode ?: $this->requestStack->getCurrentRequest()->query->get('view_mode', 'default')]
);
}
$settings = $this->getDiffSettings();
// Swap out the stock visual diff with a new one.
$this->htmlDiff->setOldHtml(
$this->preprocessMarkup(
$this->htmlDiff->getOldHtml(),
$settings
)
);
$this->htmlDiff->setNewHtml(
$this->preprocessMarkup(
$this->htmlDiff->getNewHtml(),
$settings
)
);
$this->htmlDiff->build();
$markup = Xss::filter($this->htmlDiff->getDifference(), static::HTML5_TAGS);
if ($settings['visual_html5_preserve_inline_styles']) {
$dom = Html::load($markup);
$xpath = new \DOMXPath($dom);
$elements_with_inline_styles = $xpath->query('//*[@data-diff-plus-style]');
foreach ($elements_with_inline_styles as $element_with_inline_styles) {
$element_with_inline_styles->setAttribute(
'style',
$element_with_inline_styles->getAttribute('data-diff-plus-style')
);
}
$markup = Html::serialize($dom);
}
// @todo Ask the security team about whether this is 100% safe.
$build['diff']['#markup'] = Markup::create($markup);
return $build;
}
}