Validator.php 8.2 KB

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