diff --git a/code/Model/SiteTree.php b/code/Model/SiteTree.php
index b15cbdbf41..5899d5a994 100755
--- a/code/Model/SiteTree.php
+++ b/code/Model/SiteTree.php
@@ -71,6 +71,7 @@
use SilverStripe\Versioned\Versioned;
use SilverStripe\View\ArrayData;
use SilverStripe\View\HTML;
+use SilverStripe\View\Parsers\HTMLValue;
use SilverStripe\View\Parsers\ShortcodeParser;
use SilverStripe\View\Parsers\URLSegmentFilter;
use SilverStripe\View\Shortcodes\EmbedShortcodeProvider;
@@ -1689,6 +1690,8 @@ protected function onBeforeWrite()
$this->setNextWriteWithoutVersion(true);
}
+ $this->sanitiseExtraMeta();
+
// Flush cached [embed] shortcodes
// Flush on both DRAFT and LIVE because VersionedCacheAdapter has separate caches for both
// Clear both caches at once for the scenario where a CMS-author updates a remote resource
@@ -1708,6 +1711,27 @@ protected function onBeforeWrite()
}
}
+ private function sanitiseExtraMeta(): void
+ {
+ $htmlValue = HTMLValue::create($this->ExtraMeta);
+ /** @var DOMElement $el */
+ foreach ($htmlValue->query('//*') as $el) {
+ /** @var DOMAttr $attr */
+ $attributes = $el->attributes;
+ for ($i = count($attributes) - 1; $i >= 0; $i--) {
+ $attr = $attributes->item($i);
+ // remove any attribute starting with 'on' e.g. onclick
+ // and remove the accesskey attribute
+ if (substr($attr->name, 0, 2) === 'on' ||
+ $attr->name === 'accesskey'
+ ) {
+ $el->removeAttributeNode($attr);
+ }
+ }
+ }
+ $this->ExtraMeta = $htmlValue->getContent();
+ }
+
/**
* Trigger synchronisation of link tracking
*
@@ -1802,6 +1826,16 @@ public function validate()
);
}
+ // Ensure ExtraMeta can be turned into valid HTML
+ if ($this->ExtraMeta && !HTMLValue::create($this->ExtraMeta)->getContent()) {
+ $result->addError(
+ _t(
+ 'SilverStripe\\CMS\\Model\\SiteTree.InvalidExtraMeta',
+ 'Custom Meta Tags does not contain valid HTML',
+ )
+ );
+ }
+
return $result;
}
diff --git a/lang/en.yml b/lang/en.yml
index a539d2941a..c66e1b4e8c 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -208,6 +208,7 @@ en:
HTMLEDITORTITLE: Content
INHERIT: 'Inherit from parent page'
INHERITSITECONFIG: 'Inherit from site access settings'
+ InvalidExtraMeta: 'Custom Meta Tags does not contain valid HTML'
LASTPUBLISHED: 'Last published'
LASTSAVED: 'Last saved'
LASTUPDATED: 'Last Updated'
diff --git a/tests/php/Model/SiteTreeTest.php b/tests/php/Model/SiteTreeTest.php
index 86880f7cc5..71bb02abc2 100644
--- a/tests/php/Model/SiteTreeTest.php
+++ b/tests/php/Model/SiteTreeTest.php
@@ -2017,4 +2017,51 @@ public function testCMSEditLink()
$child->CMSEditLink()
);
}
+
+ /**
+ * @dataProvider provideSanitiseExtraMeta
+ */
+ public function testSanitiseExtraMeta(string $extraMeta, string $expected, string $message): void
+ {
+ $siteTree = new SiteTree();
+ $siteTree->ExtraMeta = $extraMeta;
+ $siteTree->write();
+ $this->assertSame($expected, $siteTree->ExtraMeta, $message);
+ }
+
+ public function provideSanitiseExtraMeta(): array
+ {
+ return [
+ [
+ '',
+ '',
+ 'accesskey attribute is removed'
+ ],
+ [
+ '',
+ '',
+ 'Attributes starting with "on" are removed'
+ ],
+ [
+ '',
+ '',
+ 'Attributes with different quote styles are removed'
+ ],
+ [
+ '',
+ '',
+ 'Mixed case attributes are removed'
+ ],
+ [
+ '',
+ '',
+ 'Multiple attributes are removed'
+ ],
+ [
+ '',
+ 'Invalid HTML is converted to valid HTML and parsed'
+ ],
+ ];
+ }
}