diff --git a/docs/en/changelogs/rc/3.1.3-rc1.md b/docs/en/changelogs/rc/3.1.3-rc1.md new file mode 100644 index 000000000..7dd18cbf2 --- /dev/null +++ b/docs/en/changelogs/rc/3.1.3-rc1.md @@ -0,0 +1,13 @@ +# 3.1.3-rc1 + +## Overview + + * ExtraMeta fields can now only contain `meta` and `link` elements + +## Upgrading + +### ExtraMeta fields can now only contain `meta` and `link` elements + +Previously ExtraMeta fields could contain any HTML elements. From 3.1.3-rc1 the contents are filtered +on write to only allow `meta` and `link` elements. The first time after upgrading that you save a page +that has other elements in ExtraMeta they will be deleted. diff --git a/model/fieldtypes/HTMLText.php b/model/fieldtypes/HTMLText.php index 188ec256b..46d462aa8 100644 --- a/model/fieldtypes/HTMLText.php +++ b/model/fieldtypes/HTMLText.php @@ -34,12 +34,45 @@ class HTMLText extends Text { protected $processShortcodes = true; + protected $whitelist = false; + + public function __construct($name = null, $options = array()) { + if(is_string($options)) { + $options = array('whitelist' => $options); + } + + return parent::__construct($name, $options); + } + + /** + * @param array $options + * + * Options accepted in addition to those provided by Text: + * + * - shortcodes: If true, shortcodes will be turned into the appropriate HTML. + * If false, shortcodes will not be processed. + * + * - whitelist: If provided, a comma-separated list of elements that will be allowed to be stored + * (be careful on relying on this for XSS protection - some seemingly-safe elements allow + * attributes that can be exploited, for instance ) + * + * @return void + */ public function setOptions(array $options = array()) { parent::setOptions($options); if(array_key_exists("shortcodes", $options)) { $this->processShortcodes = !!$options["shortcodes"]; } + + if(array_key_exists("whitelist", $options)) { + if(is_array($options['whitelist'])) { + $this->whitelist = $options['whitelist']; + } + else { + $this->whitelist = preg_split('/,\s*/', $options['whitelist']); + } + } } /** @@ -149,7 +182,24 @@ class HTMLText extends Text { return $this->value; } } - + + public function prepValueForDB($value) { + if($this->whitelist) { + $dom = Injector::inst()->create('HTMLValue', $value); + + $query = array(); + foreach ($this->whitelist as $tag) $query[] = 'not(self::'.$tag.')'; + + foreach($dom->query('//body//*['.implode(' and ', $query).']') as $el) { + if ($el->parentNode) $el->parentNode->removeChild($el); + } + + $value = $dom->getContent(); + } + + return parent::prepValueForDB($value); + } + /** * Returns true if the field has meaningful content. * Excludes null content like

,

,etc diff --git a/tests/model/HTMLTextTest.php b/tests/model/HTMLTextTest.php index c182f652c..ac46cd647 100644 --- a/tests/model/HTMLTextTest.php +++ b/tests/model/HTMLTextTest.php @@ -176,4 +176,14 @@ class HTMLTextTest extends SapphireTest { $h->setValue("

test

"); $this->assertTrue($h->exists()); } + + function testWhitelist() { + $textObj = new HTMLText('Test', 'meta,link'); + + $this->assertEquals( + $textObj->prepValueForDB(''), + $textObj->prepValueForDB('

Remove

'), + 'Removes any elements not in whitelist' + ); + } }