diff --git a/library/HTMLPurifier/ConfigSchema/Interchange.php b/library/HTMLPurifier/ConfigSchema/Interchange.php index 1b1a2489..18866dbf 100644 --- a/library/HTMLPurifier/ConfigSchema/Interchange.php +++ b/library/HTMLPurifier/ConfigSchema/Interchange.php @@ -22,14 +22,20 @@ class HTMLPurifier_ConfigSchema_Interchange * Adds a namespace array to $namespaces */ public function addNamespace($namespace) { - $this->namespaces[$namespace->namespace] = $namespace; + if (isset($this->namespaces[$i = $namespace->namespace])) { + throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine namespace '$i'"); + } + $this->namespaces[$i] = $namespace; } /** * Adds a directive array to $directives */ public function addDirective($directive) { - $this->directives[$directive->id->__toString()] = $directive; + if (isset($this->directives[$i = $directive->id->__toString()])) { + throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); + } + $this->directives[$i] = $directive; } } diff --git a/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php index 6020e573..f010cdfc 100644 --- a/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php +++ b/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -15,6 +15,12 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder * @param $hash HTMLPurifier_ConfigSchema_StringHash source data */ public function build($interchange, $hash) { + if (!$hash instanceof HTMLPurifier_StringHash) { + $hash = new HTMLPurifier_StringHash($hash); + } + if (!isset($hash['ID'])) { + throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); + } if (strpos($hash['ID'], '.') === false) { $this->buildNamespace($interchange, $hash); } else { @@ -26,7 +32,9 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder public function buildNamespace($interchange, $hash) { $namespace = new HTMLPurifier_ConfigSchema_Interchange_Namespace(); $namespace->namespace = $hash->offsetGet('ID'); - $namespace->description = $hash->offsetGet('DESCRIPTION'); + if (isset($hash['DESCRIPTION'])) { + $namespace->description = $hash->offsetGet('DESCRIPTION'); + } $interchange->addNamespace($namespace); } @@ -35,33 +43,43 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder // These are required elements: $directive->id = $this->id($hash->offsetGet('ID')); - $type = explode('/', $hash->offsetGet('TYPE')); - if (isset($type[1])) $directive->typeAllowsNull = true; - $directive->type = $type[0]; - $directive->description = $directive->offsetGet('DESCRIPTION'); - // These are extras: - if (isset($directive['ALLOWED'])) { - $directive->allowed = $this->lookup($this->evalArray($directive->offsetGet('ALLOWED'))); + if (isset($hash['TYPE'])) { + $type = explode('/', $hash->offsetGet('TYPE')); + if (isset($type[1])) $directive->typeAllowsNull = true; + $directive->type = $type[0]; } - if (isset($directive['VALUE-ALIASES'])) { - $directive->valueAliases = $this->evalArray($directive->offsetGet('VALUE-ALIASES')); + + if (isset($hash['DESCRIPTION'])) { + $directive->description = $hash->offsetGet('DESCRIPTION'); } - if (isset($directive['ALIASES'])) { + + if (isset($hash['ALLOWED'])) { + $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); + } + + if (isset($hash['VALUE-ALIASES'])) { + $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); + } + + if (isset($hash['ALIASES'])) { $raw_aliases = trim($hash->offsetGet('ALIASES')); $aliases = preg_split('/\s*,\s*/', $raw_aliases); foreach ($aliases as $alias) { - $this->aliases[] = $this->id($alias); + $directive->aliases[] = $this->id($alias); } } - if (isset($directive['VERSION'])) { - $directive->version = $directive->offsetGet('VERSION'); + + if (isset($hash['VERSION'])) { + $directive->version = $hash->offsetGet('VERSION'); } - if (isset($directive['DEPRECATED-USE'])) { - $directive->deprecatedUse = $this->id($directive->offsetGet('DEPRECATED-USE')); + + if (isset($hash['DEPRECATED-USE'])) { + $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); } - if (isset($directive['DEPRECATED-VERSION'])) { - $directive->deprecatedVersion = $directive->offsetGet('DEPRECATED-VERSION'); + + if (isset($hash['DEPRECATED-VERSION'])) { + $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); } $interchange->addDirective($directive); diff --git a/library/HTMLPurifier/ConfigSchema/Validator.php b/library/HTMLPurifier/ConfigSchema/Validator.php index 48c0f82a..88125877 100644 --- a/library/HTMLPurifier/ConfigSchema/Validator.php +++ b/library/HTMLPurifier/ConfigSchema/Validator.php @@ -11,7 +11,7 @@ class HTMLPurifier_ConfigSchema_Validator /** * Volatile context variables to provide a fluent interface. */ - protected $context, $obj, $member; + protected $context = array(), $obj, $member; /** * Validates a fully-formed interchange object. Throws an @@ -28,38 +28,53 @@ class HTMLPurifier_ConfigSchema_Validator } public function validateNamespace($n) { - $this->context = "namespace '{$n->namespace}'"; + $this->context[] = "namespace '{$n->namespace}'"; $this->with($n, 'namespace') ->assertNotEmpty() ->assertAlnum(); $this->with($n, 'description') - ->assertIsString() - ->assertNotEmpty(); + ->assertNotEmpty() + ->assertIsString(); // technically redundant + array_pop($this->context); } public function validateDirective($d) { - $this->context = "directive '{$d->id}'"; + $this->context[] = "directive '{$d->id}'"; $this->validateId($d->id); + $this->with($d, 'description') + ->assertNotEmpty(); + if (!isset(HTMLPurifier_VarParser::$types[$d->type])) { + $this->error('type', 'is invalid'); + } + array_pop($this->context); } public function validateId($id) { - $this->context = "id '$id'"; + $this->context[] = "id '$id'"; + if (!isset($this->interchange->namespaces[$id->namespace])) { + $this->error('namespace', 'does not exist'); + } $this->with($id, 'namespace') ->assertNotEmpty() ->assertAlnum(); $this->with($id, 'directive') ->assertNotEmpty() ->assertAlnum(); + array_pop($this->context); } // protected helper functions protected function with($obj, $member) { - return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->context, $obj, $member); + return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); } - protected function error($msg) { - throw new HTMLPurifier_ConfigSchema_Exception($msg . ' in ' . $this->context); + protected function error($target, $msg) { + throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($target) . ' in ' . $this->getFormattedContext() . ' ' . $msg); + } + + protected function getFormattedContext() { + return implode(' in ', array_reverse($this->context)); } } diff --git a/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php index b799d67b..ac8d3e5d 100644 --- a/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php +++ b/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -40,7 +40,7 @@ class HTMLPurifier_ConfigSchema_ValidatorAtom } protected function error($msg) { - throw new HTMLPurifier_ConfigSchema_Exception('Member variable \'' . $this->member . '\' in ' . $this->context . ' ' . $msg); + throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); } } diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest new file mode 100644 index 00000000..fd2d0922 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest @@ -0,0 +1,7 @@ +ERROR: Description in directive 'Ns.Dir' must not be empty +---- +Ns +DESCRIPTION: Our namespace. +---- +Ns.Dir +TYPE: int diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/directive/idDirectiveAlnum.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/directive/idDirectiveAlnum.vtest new file mode 100644 index 00000000..9ffd143b --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/directive/idDirectiveAlnum.vtest @@ -0,0 +1,8 @@ +ERROR: Directive in id 'Ns.+' in directive 'Ns.+' must be alphanumeric +---- +Ns +DESCRIPTION: Generic namespace. +---- +ID: Ns.+ +TYPE: int +DESCRIPTION: Description diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/directive/idDirectiveNotEmpty.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/directive/idDirectiveNotEmpty.vtest new file mode 100644 index 00000000..27e11282 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/directive/idDirectiveNotEmpty.vtest @@ -0,0 +1,8 @@ +ERROR: Directive in id 'Ns.' in directive 'Ns.' must not be empty +---- +Ns +DESCRIPTION: Our namespace +---- +ID: Ns. +TYPE: int +DESCRIPTION: Description. diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/directive/idNamespaceExists.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/directive/idNamespaceExists.vtest new file mode 100644 index 00000000..b216bf8d --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/directive/idNamespaceExists.vtest @@ -0,0 +1,5 @@ +ERROR: Namespace in id 'Rd.Dir' in directive 'Rd.Dir' does not exist +---- +ID: Rd.Dir +TYPE: int +DESCRIPTION: Description diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest new file mode 100644 index 00000000..dd2ae907 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest @@ -0,0 +1,8 @@ +ERROR: Type in directive 'Ns.Dir' is invalid +---- +Ns +DESCRIPTION: Namespace +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: foobar diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest new file mode 100644 index 00000000..7fbd4744 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest @@ -0,0 +1,9 @@ +ERROR: Cannot redefine directive 'Ns.Dir' +---- +ID: Ns.Dir +DESCRIPTION: Version 1 +TYPE: int +---- +ID: Ns.Dir +DESCRIPTION: Version 2 +TYPE: int diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/namespace/descriptionNotEmpty.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/descriptionNotEmpty.vtest new file mode 100644 index 00000000..888218a0 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/descriptionNotEmpty.vtest @@ -0,0 +1,3 @@ +ERROR: Description in namespace 'Ns' must not be empty +---- +ID: Ns diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/namespace/namespaceAlnum.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/namespaceAlnum.vtest new file mode 100644 index 00000000..5b914f75 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/namespaceAlnum.vtest @@ -0,0 +1,4 @@ +ERROR: Namespace in namespace 'R&D' must be alphanumeric +---- +ID: R&D +DESCRIPTION: ctype_alnum($ID) === false diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/namespace/namespaceNotEmpty.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/namespaceNotEmpty.vtest new file mode 100644 index 00000000..42ba5baa --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/namespaceNotEmpty.vtest @@ -0,0 +1,4 @@ +ERROR: Namespace in namespace '0' must not be empty +---- +ID: 0 +DESCRIPTION: empty($ID) === true diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/namespace/normal.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/normal.vtest new file mode 100644 index 00000000..13238143 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/normal.vtest @@ -0,0 +1,2 @@ +Namespace +DESCRIPTION: This is a generic namespace. diff --git a/tests/HTMLPurifier/ConfigSchema/Validator/namespace/unique.vtest b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/unique.vtest new file mode 100644 index 00000000..1d1f12d1 --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/Validator/namespace/unique.vtest @@ -0,0 +1,7 @@ +ERROR: Cannot redefine namespace 'Ns' +---- +ID: Ns +DESCRIPTION: Version 1 +---- +ID: Ns +DESCRIPTION: Version 2 diff --git a/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php b/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php index 7dc027f9..fe6cc262 100644 --- a/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php +++ b/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php @@ -19,7 +19,7 @@ class HTMLPurifier_ConfigSchema_ValidatorAtomTest extends UnitTestCase } public function testAssertIsStringFail() { - $this->expectValidationException("Member variable 'property' in context must be a string"); + $this->expectValidationException("Property in context must be a string"); $this->makeAtom(3)->assertIsString(); } @@ -28,7 +28,7 @@ class HTMLPurifier_ConfigSchema_ValidatorAtomTest extends UnitTestCase } public function testAssertNotNullFail() { - $this->expectValidationException("Member variable 'property' in context must not be null"); + $this->expectValidationException("Property in context must not be null"); $this->makeAtom(null)->assertNotNull(); } @@ -37,12 +37,12 @@ class HTMLPurifier_ConfigSchema_ValidatorAtomTest extends UnitTestCase } public function testAssertAlnumFail() { - $this->expectValidationException("Member variable 'property' in context must be alphanumeric"); + $this->expectValidationException("Property in context must be alphanumeric"); $this->makeAtom('%a')->assertAlnum(); } public function testAssertAlnumFailIsString() { - $this->expectValidationException("Member variable 'property' in context must be a string"); + $this->expectValidationException("Property in context must be a string"); $this->makeAtom(3)->assertAlnum(); } @@ -51,7 +51,7 @@ class HTMLPurifier_ConfigSchema_ValidatorAtomTest extends UnitTestCase } public function testAssertNotEmptyFail() { - $this->expectValidationException("Member variable 'property' in context must not be empty"); + $this->expectValidationException("Property in context must not be empty"); $this->makeAtom('')->assertNotEmpty(); } diff --git a/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php b/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php deleted file mode 100644 index 11404b38..00000000 --- a/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php +++ /dev/null @@ -1,75 +0,0 @@ -interchange = new HTMLPurifier_ConfigSchema_Interchange(); - $this->validator = new HTMLPurifier_ConfigSchema_Validator(); - } - - /** - * Adds a namespace to our interchange object. - */ - protected function addNamespace($namespace, $description) { - $obj = new HTMLPurifier_ConfigSchema_Interchange_Namespace(); - $obj->namespace = $namespace; - $obj->description = $description; - $this->interchange->addNamespace($obj); - return $obj; - } - - protected function addDirective($namespace, $directive, $type = 'string', $description = 'Description') { - $obj = new HTMLPurifier_ConfigSchema_Interchange_Directive(); - $obj->id = $this->makeId($namespace, $directive); - $obj->type = $type; - $obj->description = $description; - $this->interchange->addDirective($obj); - return $obj; // for future editing - } - - protected function makeId($namespace, $directive) { - return new HTMLPurifier_ConfigSchema_Interchange_Id($namespace, $directive); - } - - /** - * Invokes a validation, so we can fish for exceptions - */ - protected function validate() { - $this->validator->validate($this->interchange); - } - - protected function expectValidationException($msg) { - $this->expectException(new HTMLPurifier_ConfigSchema_Exception($msg)); - } - - public function testNamespace() { - $this->addNamespace('Namespace', 'This is a generic namespace'); - $this->validate(); - } - - public function testNamespaceNamespaceNotEmpty() { - $this->addNamespace('0', 'Description'); - $this->expectValidationException("Member variable 'namespace' in namespace '0' must not be empty"); - $this->validate(); - } - - public function testNamespaceNamespaceAlnum() { - $this->addNamespace('%', 'Description'); - $this->expectValidationException("Member variable 'namespace' in namespace '%' must be alphanumeric"); - $this->validate(); - } - - public function testNamespaceDescriptionNotEmpty() { - $this->addNamespace('Ns', ''); - $this->expectValidationException("Member variable 'description' in namespace 'Ns' must not be empty"); - $this->validate(); - } - - public function testDirectiveIdNamespaceNotEmpty() { - $this->addDirective('', 'Dir'); - } - -} diff --git a/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php b/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php new file mode 100644 index 00000000..eb016eed --- /dev/null +++ b/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php @@ -0,0 +1,42 @@ +_path = $path; + $this->_parser = new HTMLPurifier_StringHashParser(); + $this->_builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); + parent::UnitTestCase($path); + } + + public function setup() { + $this->validator = new HTMLPurifier_ConfigSchema_Validator(); + } + + public function testValidator() { + $hashes = $this->_parser->parseMultiFile($this->_path); + $interchange = new HTMLPurifier_ConfigSchema_Interchange(); + $error = null; + foreach ($hashes as $hash) { + if (!isset($hash['ID'])) { + if (isset($hash['ERROR'])) { + $this->expectException( + new HTMLPurifier_ConfigSchema_Exception($hash['ERROR']) + ); + } + continue; + } + $this->_builder->build($interchange, new HTMLPurifier_StringHash($hash)); + } + $this->validator->validate($interchange); + $this->pass(); + } + +} diff --git a/tests/common.php b/tests/common.php index 31820d19..9fc987ff 100644 --- a/tests/common.php +++ b/tests/common.php @@ -156,6 +156,8 @@ function htmlpurifier_add_test($test, $test_file, $only_phpt = false) { case '.php': require_once $test_file; return $test->addTestClass(path2class($test_file)); + case '.vtest': + return $test->addTestCase(new HTMLPurifier_ConfigSchema_ValidatorTestCase($test_file)); default: trigger_error("$test_file is an invalid file for testing", E_USER_ERROR); } diff --git a/tests/index.php b/tests/index.php index b045b804..cfd6bf4c 100755 --- a/tests/index.php +++ b/tests/index.php @@ -110,6 +110,14 @@ foreach ($test_dirs as $dir) { } } +// handle vtest dirs +foreach ($vtest_dirs as $dir) { + $raw_files = $FS->globr($dir, '*.vtest'); + foreach ($raw_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + // handle phpt files foreach ($phpt_dirs as $dir) { $phpt_files = $FS->globr($dir, '*.phpt'); diff --git a/tests/test_files.php b/tests/test_files.php index 210112e2..def93e22 100644 --- a/tests/test_files.php +++ b/tests/test_files.php @@ -15,6 +15,10 @@ if (!$AC['only-phpt']) { $test_files[] = 'HTMLPurifier/Filter/ExtractStyleBlocksTest.php'; } + // ConfigSchema Validator tests + $vtest_dirs = array(); + $vtest_dirs[] = 'HTMLPurifier/ConfigSchema/Validator'; + // ConfigDoc auxiliary library if (version_compare(PHP_VERSION, '5.2', '>=')) { $test_dirs[] = 'ConfigDoc';