- This directive complements %HTML.ForbiddenElements and is the inverse of
- %HTML.AllowedAttributes. Please see the former for a discussion of why you
+ While this directive is similar to %HTML.AllowedAttributes, for
+ forwards-compatibility with XML, this attribute has a different syntax. Instead of
+ tag.attr
, use tag@attr
. To disallow href
+ attributes in a
tags, set this directive to
+ a@href
. You can also disallow an attribute globally with
+ attr
or *@attr
(either syntax is fine; the latter
+ is provided for consistency with %HTML.AllowedAttributes).
+
+ Warning: This directive complements %HTML.ForbiddenElements, + accordingly, check + out that directive for a discussion of why you should think twice before using this directive.
diff --git a/library/HTMLPurifier/HTMLDefinition.php b/library/HTMLPurifier/HTMLDefinition.php index 33768396..3310bbc2 100644 --- a/library/HTMLPurifier/HTMLDefinition.php +++ b/library/HTMLPurifier/HTMLDefinition.php @@ -233,10 +233,10 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition $support = "(for information on implementing this, see the ". "support forums) "; - // setup allowed elements + // setup allowed elements ----------------------------------------- $allowed_elements = $config->get('HTML', 'AllowedElements'); - $allowed_attributes = $config->get('HTML', 'AllowedAttributes'); + $allowed_attributes = $config->get('HTML', 'AllowedAttributes'); // retrieve early if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { $allowed = $config->get('HTML', 'Allowed'); @@ -252,55 +252,80 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition } // emit errors foreach ($allowed_elements as $element => $d) { - // :TODO: Is this htmlspecialchars() call really necessary? - $element = htmlspecialchars($element); + $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! trigger_error("Element '$element' is not supported $support", E_USER_WARNING); } } + // setup allowed attributes --------------------------------------- + $allowed_attributes_mutable = $allowed_attributes; // by copy! if (is_array($allowed_attributes)) { - foreach ($this->info_global_attr as $attr_key => $info) { - if (!isset($allowed_attributes["*.$attr_key"])) { - unset($this->info_global_attr[$attr_key]); - } elseif (isset($allowed_attributes_mutable["*.$attr_key"])) { - unset($allowed_attributes_mutable["*.$attr_key"]); + + // This actually doesn't do anything, since we went away from + // global attributes. It's possible that userland code uses + // it, but HTMLModuleManager doesn't! + foreach ($this->info_global_attr as $attr => $x) { + $keys = array($attr, "*@$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } } + if ($delete) unset($this->info_global_attr[$attr]); } + foreach ($this->info as $tag => $info) { - foreach ($info->attr as $attr => $attr_info) { - if (!isset($allowed_attributes["$tag.$attr"]) && - !isset($allowed_attributes["*.$attr"])) { - unset($this->info[$tag]->attr[$attr]); - } else { - if (isset($allowed_attributes_mutable["$tag.$attr"])) { - unset($allowed_attributes_mutable["$tag.$attr"]); - } elseif (isset($allowed_attributes_mutable["*.$attr"])) { - unset($allowed_attributes_mutable["*.$attr"]); + foreach ($info->attr as $attr => $x) { + $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); } } + if ($delete) unset($this->info[$tag]->attr[$attr]); } } // emit errors foreach ($allowed_attributes_mutable as $elattr => $d) { - list($element, $attribute) = explode('.', $elattr); - // :TODO: Is this htmlspecialchars() call really necessary? - $element = htmlspecialchars($element); - $attribute = htmlspecialchars($attribute); - if ($element == '*') { - trigger_error("Global attribute '$attribute' is not ". - "supported in any elements $support", - E_USER_WARNING); - } else { - trigger_error("Attribute '$attribute' in element '$element' not supported $support", - E_USER_WARNING); + $bits = preg_split('/[.@]/', $elattr, 2); + $c = count($bits); + switch ($c) { + case 2: + if ($bits[0] !== '*') { + $element = htmlspecialchars($bits[0]); + $attribute = htmlspecialchars($bits[1]); + if (!isset($this->info[$element])) { + trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support"); + } else { + trigger_error("Attribute '$attribute' in element '$element' not supported $support", + E_USER_WARNING); + } + break; + } + // otherwise fall through + case 1: + $attribute = htmlspecialchars($bits[0]); + trigger_error("Global attribute '$attribute' is not ". + "supported in any elements $support", + E_USER_WARNING); + break; } } } - // setup forbidden elements - $forbidden_elements = $config->get('HTML', 'ForbiddenElements'); + // setup forbidden elements --------------------------------------- + + $forbidden_elements = $config->get('HTML', 'ForbiddenElements'); $forbidden_attributes = $config->get('HTML', 'ForbiddenAttributes'); foreach ($this->info as $tag => $info) { @@ -308,10 +333,18 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition unset($this->info[$tag]); continue; } - foreach ($info->attr as $name => $def) { - if (isset($forbidden_attributes["$tag.$name"])) { - unset($this->info[$tag]->attr[$name]); + foreach ($info->attr as $attr => $x) { + if ( + isset($forbidden_attributes["$tag@$attr"]) || + isset($forbidden_attributes["*@$attr"]) || + isset($forbidden_attributes[$attr]) + ) { + unset($this->info[$tag]->attr[$attr]); continue; + } // this segment might get removed eventually + elseif (isset($forbidden_attributes["$tag.$attr"])) { + // $tag.$attr are not user supplied, so no worries! + trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead", E_USER_WARNING); } } } diff --git a/tests/HTMLPurifier/HTMLDefinitionTest.php b/tests/HTMLPurifier/HTMLDefinitionTest.php index 42010f58..5b4f5e77 100644 --- a/tests/HTMLPurifier/HTMLDefinitionTest.php +++ b/tests/HTMLPurifier/HTMLDefinitionTest.php @@ -59,7 +59,7 @@ a[href|title] $config1 = HTMLPurifier_Config::create(array( 'HTML.AllowedElements' => array('b', 'i', 'p', 'a'), - 'HTML.AllowedAttributes' => array('a.href', '*.id') + 'HTML.AllowedAttributes' => array('a@href', '*@id') )); $config2 = HTMLPurifier_Config::create(array( @@ -70,6 +70,150 @@ a[href|title] } + function assertPurification_AllowedElements_p() { + $this->assertPurification('Jelly
', 'Jelly
'); + } + + function test_AllowedElements() { + $this->config->set('HTML', 'AllowedElements', 'p'); + $this->assertPurification_AllowedElements_p(); + } + + function test_AllowedElements_multiple() { + $this->config->set('HTML', 'AllowedElements', 'p,div'); + $this->assertPurification('Jelly
Jelly