--- /dev/null
+<?php\r
+\r
+/**\r
+ * Performs validations on HTMLPurifier_ConfigSchema_Interchange\r
+ *\r
+ * @note If you see '// handled by InterchangeBuilder', that means a\r
+ * design decision in that class would prevent this validation from\r
+ * ever being necessary. We have them anyway, however, for\r
+ * redundancy.\r
+ */\r
+class HTMLPurifier_ConfigSchema_Validator\r
+{\r
+\r
+ /**\r
+ * @type HTMLPurifier_ConfigSchema_Interchange\r
+ */\r
+ protected $interchange;\r
+\r
+ /**\r
+ * @type array\r
+ */\r
+ protected $aliases;\r
+\r
+ /**\r
+ * Context-stack to provide easy to read error messages.\r
+ * @type array\r
+ */\r
+ protected $context = array();\r
+\r
+ /**\r
+ * to test default's type.\r
+ * @type HTMLPurifier_VarParser\r
+ */\r
+ protected $parser;\r
+\r
+ public function __construct()\r
+ {\r
+ $this->parser = new HTMLPurifier_VarParser();\r
+ }\r
+\r
+ /**\r
+ * Validates a fully-formed interchange object.\r
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange\r
+ * @return bool\r
+ */\r
+ public function validate($interchange)\r
+ {\r
+ $this->interchange = $interchange;\r
+ $this->aliases = array();\r
+ // PHP is a bit lax with integer <=> string conversions in\r
+ // arrays, so we don't use the identical !== comparison\r
+ foreach ($interchange->directives as $i => $directive) {\r
+ $id = $directive->id->toString();\r
+ if ($i != $id) {\r
+ $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");\r
+ }\r
+ $this->validateDirective($directive);\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.\r
+ * @param HTMLPurifier_ConfigSchema_Interchange_Id $id\r
+ */\r
+ public function validateId($id)\r
+ {\r
+ $id_string = $id->toString();\r
+ $this->context[] = "id '$id_string'";\r
+ if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {\r
+ // handled by InterchangeBuilder\r
+ $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id');\r
+ }\r
+ // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.)\r
+ // we probably should check that it has at least one namespace\r
+ $this->with($id, 'key')\r
+ ->assertNotEmpty()\r
+ ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder\r
+ array_pop($this->context);\r
+ }\r
+\r
+ /**\r
+ * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.\r
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d\r
+ */\r
+ public function validateDirective($d)\r
+ {\r
+ $id = $d->id->toString();\r
+ $this->context[] = "directive '$id'";\r
+ $this->validateId($d->id);\r
+\r
+ $this->with($d, 'description')\r
+ ->assertNotEmpty();\r
+\r
+ // BEGIN - handled by InterchangeBuilder\r
+ $this->with($d, 'type')\r
+ ->assertNotEmpty();\r
+ $this->with($d, 'typeAllowsNull')\r
+ ->assertIsBool();\r
+ try {\r
+ // This also tests validity of $d->type\r
+ $this->parser->parse($d->default, $d->type, $d->typeAllowsNull);\r
+ } catch (HTMLPurifier_VarParserException $e) {\r
+ $this->error('default', 'had error: ' . $e->getMessage());\r
+ }\r
+ // END - handled by InterchangeBuilder\r
+\r
+ if (!is_null($d->allowed) || !empty($d->valueAliases)) {\r
+ // allowed and valueAliases require that we be dealing with\r
+ // strings, so check for that early.\r
+ $d_int = HTMLPurifier_VarParser::$types[$d->type];\r
+ if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) {\r
+ $this->error('type', 'must be a string type when used with allowed or value aliases');\r
+ }\r
+ }\r
+\r
+ $this->validateDirectiveAllowed($d);\r
+ $this->validateDirectiveValueAliases($d);\r
+ $this->validateDirectiveAliases($d);\r
+\r
+ array_pop($this->context);\r
+ }\r
+\r
+ /**\r
+ * Extra validation if $allowed member variable of\r
+ * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.\r
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d\r
+ */\r
+ public function validateDirectiveAllowed($d)\r
+ {\r
+ if (is_null($d->allowed)) {\r
+ return;\r
+ }\r
+ $this->with($d, 'allowed')\r
+ ->assertNotEmpty()\r
+ ->assertIsLookup(); // handled by InterchangeBuilder\r
+ if (is_string($d->default) && !isset($d->allowed[$d->default])) {\r
+ $this->error('default', 'must be an allowed value');\r
+ }\r
+ $this->context[] = 'allowed';\r
+ foreach ($d->allowed as $val => $x) {\r
+ if (!is_string($val)) {\r
+ $this->error("value $val", 'must be a string');\r
+ }\r
+ }\r
+ array_pop($this->context);\r
+ }\r
+\r
+ /**\r
+ * Extra validation if $valueAliases member variable of\r
+ * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.\r
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d\r
+ */\r
+ public function validateDirectiveValueAliases($d)\r
+ {\r
+ if (is_null($d->valueAliases)) {\r
+ return;\r
+ }\r
+ $this->with($d, 'valueAliases')\r
+ ->assertIsArray(); // handled by InterchangeBuilder\r
+ $this->context[] = 'valueAliases';\r
+ foreach ($d->valueAliases as $alias => $real) {\r
+ if (!is_string($alias)) {\r
+ $this->error("alias $alias", 'must be a string');\r
+ }\r
+ if (!is_string($real)) {\r
+ $this->error("alias target $real from alias '$alias'", 'must be a string');\r
+ }\r
+ if ($alias === $real) {\r
+ $this->error("alias '$alias'", "must not be an alias to itself");\r
+ }\r
+ }\r
+ if (!is_null($d->allowed)) {\r
+ foreach ($d->valueAliases as $alias => $real) {\r
+ if (isset($d->allowed[$alias])) {\r
+ $this->error("alias '$alias'", 'must not be an allowed value');\r
+ } elseif (!isset($d->allowed[$real])) {\r
+ $this->error("alias '$alias'", 'must be an alias to an allowed value');\r
+ }\r
+ }\r
+ }\r
+ array_pop($this->context);\r
+ }\r
+\r
+ /**\r
+ * Extra validation if $aliases member variable of\r
+ * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.\r
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d\r
+ */\r
+ public function validateDirectiveAliases($d)\r
+ {\r
+ $this->with($d, 'aliases')\r
+ ->assertIsArray(); // handled by InterchangeBuilder\r
+ $this->context[] = 'aliases';\r
+ foreach ($d->aliases as $alias) {\r
+ $this->validateId($alias);\r
+ $s = $alias->toString();\r
+ if (isset($this->interchange->directives[$s])) {\r
+ $this->error("alias '$s'", 'collides with another directive');\r
+ }\r
+ if (isset($this->aliases[$s])) {\r
+ $other_directive = $this->aliases[$s];\r
+ $this->error("alias '$s'", "collides with alias for directive '$other_directive'");\r
+ }\r
+ $this->aliases[$s] = $d->id->toString();\r
+ }\r
+ array_pop($this->context);\r
+ }\r
+\r
+ // protected helper functions\r
+\r
+ /**\r
+ * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom\r
+ * for validating simple member variables of objects.\r
+ * @param $obj\r
+ * @param $member\r
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom\r
+ */\r
+ protected function with($obj, $member)\r
+ {\r
+ return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);\r
+ }\r
+\r
+ /**\r
+ * Emits an error, providing helpful context.\r
+ * @throws HTMLPurifier_ConfigSchema_Exception\r
+ */\r
+ protected function error($target, $msg)\r
+ {\r
+ if ($target !== false) {\r
+ $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();\r
+ } else {\r
+ $prefix = ucfirst($this->getFormattedContext());\r
+ }\r
+ throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));\r
+ }\r
+\r
+ /**\r
+ * Returns a formatted context string.\r
+ * @return string\r
+ */\r
+ protected function getFormattedContext()\r
+ {\r
+ return implode(' in ', array_reverse($this->context));\r
+ }\r
+}\r
+\r
+// vim: et sw=4 sts=4\r