diff --git a/NEWS b/NEWS index 2b2a06e3..f5943431 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,7 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier . Refactored unit tests to minimize duplication . XSS attack sheet updated . configdoc.xml now has xml:space attached to default value nodes +. Allow configuration directives to permit null values 1.1.3, unknown projected release date (bugfix release, may be dropped if no major bugs are found before features) diff --git a/configdoc/generate.php b/configdoc/generate.php index acb059ef..93328356 100644 --- a/configdoc/generate.php +++ b/configdoc/generate.php @@ -110,9 +110,12 @@ foreach($schema->info as $namespace_name => $namespace_info) { $dom_constraints = $dom_document->createElement('constraints'); $dom_directive->appendChild($dom_constraints); - $dom_constraints->appendChild( - $dom_document->createElement('type', $info->type) - ); + $dom_type = $dom_document->createElement('type', $info->type); + if ($info->allow_null) { + $dom_type->setAttribute('allow-null', 'yes'); + } + $dom_constraints->appendChild($dom_type); + if ($info->allowed !== true) { $dom_allowed = $dom_document->createElement('allowed'); $dom_constraints->appendChild($dom_allowed); @@ -128,6 +131,8 @@ foreach($schema->info as $namespace_name => $namespace_info) { $default = $raw_default ? 'true' : 'false'; } elseif (is_string($raw_default)) { $default = "\"$raw_default\""; + } elseif (is_null($raw_default)) { + $default = 'null'; } else { $default = print_r( $schema->defaults[$namespace_name][$name], true diff --git a/configdoc/styles/plain.xsl b/configdoc/styles/plain.xsl index f97970c0..13f57352 100644 --- a/configdoc/styles/plain.xsl +++ b/configdoc/styles/plain.xsl @@ -69,7 +69,7 @@ -

+

@@ -99,6 +99,9 @@ type type- + + (or null) + diff --git a/library/HTMLPurifier/Config.php b/library/HTMLPurifier/Config.php index 18631cc8..b2ac0e70 100644 --- a/library/HTMLPurifier/Config.php +++ b/library/HTMLPurifier/Config.php @@ -80,8 +80,11 @@ class HTMLPurifier_Config E_USER_WARNING); return; } - $value = $this->def->validate($value, - $this->def->info[$namespace][$key]->type); + $value = $this->def->validate( + $value, + $this->def->info[$namespace][$key]->type, + $this->def->info[$namespace][$key]->allow_null + ); if (is_string($value)) { // resolve value alias if defined if (isset($this->def->info[$namespace][$key]->aliases[$value])) { @@ -95,7 +98,7 @@ class HTMLPurifier_Config } } } - if ($value === null) { + if ($this->def->isError($value)) { trigger_error('Value is of invalid type', E_USER_WARNING); return; } diff --git a/library/HTMLPurifier/ConfigSchema.php b/library/HTMLPurifier/ConfigSchema.php index 99806bd9..79633b20 100644 --- a/library/HTMLPurifier/ConfigSchema.php +++ b/library/HTMLPurifier/ConfigSchema.php @@ -1,5 +1,7 @@ types[$type])) { trigger_error('Invalid type for configuration directive', E_USER_ERROR); return; } - if ($def->validate($default, $type) === null) { + $default = $def->validate($default, $type, $allow_null); + if ($def->isError($default)) { trigger_error('Default value does not match directive type', E_USER_ERROR); return; @@ -124,6 +133,7 @@ class HTMLPurifier_ConfigSchema { $def->info[$namespace][$name] = new HTMLPurifier_ConfigEntity_Directive(); $def->info[$namespace][$name]->type = $type; + $def->info[$namespace][$name]->allow_null = $allow_null; $def->defaults[$namespace][$name] = $default; } $backtrace = debug_backtrace(); @@ -212,36 +222,37 @@ class HTMLPurifier_ConfigSchema { /** * Validate a variable according to type. Return null if invalid. */ - function validate($var, $type) { + function validate($var, $type, $allow_null = false) { if (!isset($this->types[$type])) { trigger_error('Invalid type', E_USER_ERROR); return; } + if ($allow_null && $var === null) return null; switch ($type) { case 'mixed': return $var; case 'istring': case 'string': - if (!is_string($var)) return; + if (!is_string($var)) break; if ($type === 'istring') $var = strtolower($var); return $var; case 'int': if (is_string($var) && ctype_digit($var)) $var = (int) $var; - elseif (!is_int($var)) return; + elseif (!is_int($var)) break; return $var; case 'float': if (is_string($var) && is_numeric($var)) $var = (float) $var; - elseif (!is_float($var)) return; + elseif (!is_float($var)) break; return $var; case 'bool': if (is_int($var) && ($var === 0 || $var === 1)) { $var = (bool) $var; - } elseif (!is_bool($var)) return; + } elseif (!is_bool($var)) break; return $var; case 'list': case 'hash': case 'lookup': - if (!is_array($var)) return; + if (!is_array($var)) break; $keys = array_keys($var); if ($keys === array_keys($keys)) { if ($type == 'list') return $var; @@ -251,7 +262,7 @@ class HTMLPurifier_ConfigSchema { $new[$key] = true; } return $new; - } else return; + } else break; } if ($type === 'lookup') { foreach ($var as $key => $value) { @@ -260,8 +271,13 @@ class HTMLPurifier_ConfigSchema { } return $var; } + $error = new HTMLPurifier_Error(); + return $error; } + /** + * Takes an absolute path and munges it into a more manageable relative path + */ function mungeFilename($filename) { $offset = strrpos($filename, 'HTMLPurifier'); $filename = substr($filename, $offset); @@ -269,6 +285,14 @@ class HTMLPurifier_ConfigSchema { return $filename; } + /** + * Checks if var is an HTMLPurifier_Error object + */ + function isError($var) { + if (!is_object($var)) return false; + if (!is_a($var, 'HTMLPurifier_Error')) return false; + return true; + } } /** @@ -318,6 +342,13 @@ class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity * - mixed (anything goes) */ var $type = 'mixed'; + + /** + * Is null allowed? Has no affect for mixed type. + * @bool + */ + var $allow_null = false; + /** * Plaintext descriptions of the configuration entity is. Organized by * file and line number, so multiple descriptions are allowed. diff --git a/library/HTMLPurifier/Error.php b/library/HTMLPurifier/Error.php new file mode 100644 index 00000000..adc81dc5 --- /dev/null +++ b/library/HTMLPurifier/Error.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/tests/HTMLPurifier/ConfigSchemaTest.php b/tests/HTMLPurifier/ConfigSchemaTest.php index fe04f053..364caa91 100644 --- a/tests/HTMLPurifier/ConfigSchemaTest.php +++ b/tests/HTMLPurifier/ConfigSchemaTest.php @@ -231,6 +231,17 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase $this->swallowErrors(); + + // define a directive that allows null + HTMLPurifier_ConfigSchema::define( + 'Core', 'Foobaz', null, 'string/null', + 'Nulls are allowed if you add on /null, cool huh?' + ); + + $this->assertNoErrors(); + $this->swallowErrors(); + + // define a directive with bad characters HTMLPurifier_ConfigSchema::define( 'Core', 'Core.Attr', 10, 'int', @@ -258,7 +269,11 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase } function assertInvalid($var, $type) { - $this->assertIdentical($this->our_copy->validate($var, $type), null); + $this->assertTrue( + $this->our_copy->isError( + $this->our_copy->validate($var, $type) + ) + ); } function testValidate() { @@ -271,6 +286,7 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase $this->assertValid(0, 'bool', false); $this->assertValid(1, 'bool', true); $this->assertInvalid(34, 'bool'); + $this->assertInvalid(null, 'bool'); $this->assertValid(array('1', '2', '3'), 'list'); $this->assertValid(array('1' => true, '2' => true), 'lookup'); $this->assertValid(array('1', '2'), 'lookup', array('1' => true, '2' => true)); @@ -281,6 +297,22 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase } + function testValidate_null() { + + $this->assertTrue( + $this->our_copy->isError( + $this->our_copy->validate(null, 'string', false) + ) + ); + + $this->assertFalse( + $this->our_copy->isError( + $this->our_copy->validate(null, 'string', true) + ) + ); + + } + function assertMungeFilename($oldname, $newname) { $this->assertIdentical( $this->our_copy->mungeFilename($oldname),