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),