Validator.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. /**
  3. * Performs validations on HTMLPurifier_ConfigSchema_Interchange
  4. *
  5. * @note If you see '// handled by InterchangeBuilder', that means a
  6. * design decision in that class would prevent this validation from
  7. * ever being necessary. We have them anyway, however, for
  8. * redundancy.
  9. */
  10. class HTMLPurifier_ConfigSchema_Validator
  11. {
  12. /**
  13. * Easy to access global objects.
  14. */
  15. protected $interchange, $aliases;
  16. /**
  17. * Context-stack to provide easy to read error messages.
  18. */
  19. protected $context = array();
  20. /**
  21. * HTMLPurifier_VarParser to test default's type.
  22. */
  23. protected $parser;
  24. public function __construct() {
  25. $this->parser = new HTMLPurifier_VarParser();
  26. }
  27. /**
  28. * Validates a fully-formed interchange object. Throws an
  29. * HTMLPurifier_ConfigSchema_Exception if there's a problem.
  30. */
  31. public function validate($interchange) {
  32. $this->interchange = $interchange;
  33. $this->aliases = array();
  34. // PHP is a bit lax with integer <=> string conversions in
  35. // arrays, so we don't use the identical !== comparison
  36. foreach ($interchange->directives as $i => $directive) {
  37. $id = $directive->id->toString();
  38. if ($i != $id) $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
  39. $this->validateDirective($directive);
  40. }
  41. return true;
  42. }
  43. /**
  44. * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.
  45. */
  46. public function validateId($id) {
  47. $id_string = $id->toString();
  48. $this->context[] = "id '$id_string'";
  49. if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {
  50. // handled by InterchangeBuilder
  51. $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id');
  52. }
  53. // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.)
  54. // we probably should check that it has at least one namespace
  55. $this->with($id, 'key')
  56. ->assertNotEmpty()
  57. ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder
  58. array_pop($this->context);
  59. }
  60. /**
  61. * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.
  62. */
  63. public function validateDirective($d) {
  64. $id = $d->id->toString();
  65. $this->context[] = "directive '$id'";
  66. $this->validateId($d->id);
  67. $this->with($d, 'description')
  68. ->assertNotEmpty();
  69. // BEGIN - handled by InterchangeBuilder
  70. $this->with($d, 'type')
  71. ->assertNotEmpty();
  72. $this->with($d, 'typeAllowsNull')
  73. ->assertIsBool();
  74. try {
  75. // This also tests validity of $d->type
  76. $this->parser->parse($d->default, $d->type, $d->typeAllowsNull);
  77. } catch (HTMLPurifier_VarParserException $e) {
  78. $this->error('default', 'had error: ' . $e->getMessage());
  79. }
  80. // END - handled by InterchangeBuilder
  81. if (!is_null($d->allowed) || !empty($d->valueAliases)) {
  82. // allowed and valueAliases require that we be dealing with
  83. // strings, so check for that early.
  84. $d_int = HTMLPurifier_VarParser::$types[$d->type];
  85. if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) {
  86. $this->error('type', 'must be a string type when used with allowed or value aliases');
  87. }
  88. }
  89. $this->validateDirectiveAllowed($d);
  90. $this->validateDirectiveValueAliases($d);
  91. $this->validateDirectiveAliases($d);
  92. array_pop($this->context);
  93. }
  94. /**
  95. * Extra validation if $allowed member variable of
  96. * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
  97. */
  98. public function validateDirectiveAllowed($d) {
  99. if (is_null($d->allowed)) return;
  100. $this->with($d, 'allowed')
  101. ->assertNotEmpty()
  102. ->assertIsLookup(); // handled by InterchangeBuilder
  103. if (is_string($d->default) && !isset($d->allowed[$d->default])) {
  104. $this->error('default', 'must be an allowed value');
  105. }
  106. $this->context[] = 'allowed';
  107. foreach ($d->allowed as $val => $x) {
  108. if (!is_string($val)) $this->error("value $val", 'must be a string');
  109. }
  110. array_pop($this->context);
  111. }
  112. /**
  113. * Extra validation if $valueAliases member variable of
  114. * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
  115. */
  116. public function validateDirectiveValueAliases($d) {
  117. if (is_null($d->valueAliases)) return;
  118. $this->with($d, 'valueAliases')
  119. ->assertIsArray(); // handled by InterchangeBuilder
  120. $this->context[] = 'valueAliases';
  121. foreach ($d->valueAliases as $alias => $real) {
  122. if (!is_string($alias)) $this->error("alias $alias", 'must be a string');
  123. if (!is_string($real)) $this->error("alias target $real from alias '$alias'", 'must be a string');
  124. if ($alias === $real) {
  125. $this->error("alias '$alias'", "must not be an alias to itself");
  126. }
  127. }
  128. if (!is_null($d->allowed)) {
  129. foreach ($d->valueAliases as $alias => $real) {
  130. if (isset($d->allowed[$alias])) {
  131. $this->error("alias '$alias'", 'must not be an allowed value');
  132. } elseif (!isset($d->allowed[$real])) {
  133. $this->error("alias '$alias'", 'must be an alias to an allowed value');
  134. }
  135. }
  136. }
  137. array_pop($this->context);
  138. }
  139. /**
  140. * Extra validation if $aliases member variable of
  141. * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
  142. */
  143. public function validateDirectiveAliases($d) {
  144. $this->with($d, 'aliases')
  145. ->assertIsArray(); // handled by InterchangeBuilder
  146. $this->context[] = 'aliases';
  147. foreach ($d->aliases as $alias) {
  148. $this->validateId($alias);
  149. $s = $alias->toString();
  150. if (isset($this->interchange->directives[$s])) {
  151. $this->error("alias '$s'", 'collides with another directive');
  152. }
  153. if (isset($this->aliases[$s])) {
  154. $other_directive = $this->aliases[$s];
  155. $this->error("alias '$s'", "collides with alias for directive '$other_directive'");
  156. }
  157. $this->aliases[$s] = $d->id->toString();
  158. }
  159. array_pop($this->context);
  160. }
  161. // protected helper functions
  162. /**
  163. * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom
  164. * for validating simple member variables of objects.
  165. */
  166. protected function with($obj, $member) {
  167. return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
  168. }
  169. /**
  170. * Emits an error, providing helpful context.
  171. */
  172. protected function error($target, $msg) {
  173. if ($target !== false) $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();
  174. else $prefix = ucfirst($this->getFormattedContext());
  175. throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));
  176. }
  177. /**
  178. * Returns a formatted context string.
  179. */
  180. protected function getFormattedContext() {
  181. return implode(' in ', array_reverse($this->context));
  182. }
  183. }
  184. // vim: et sw=4 sts=4