File indexing completed on 2025-02-02 05:43:42
0001 <?php 0002 0003 /** 0004 * Performs validations on HTMLPurifier_ConfigSchema_Interchange 0005 * 0006 * @note If you see '// handled by InterchangeBuilder', that means a 0007 * design decision in that class would prevent this validation from 0008 * ever being necessary. We have them anyway, however, for 0009 * redundancy. 0010 */ 0011 class HTMLPurifier_ConfigSchema_Validator 0012 { 0013 0014 /** 0015 * @type HTMLPurifier_ConfigSchema_Interchange 0016 */ 0017 protected $interchange; 0018 0019 /** 0020 * @type array 0021 */ 0022 protected $aliases; 0023 0024 /** 0025 * Context-stack to provide easy to read error messages. 0026 * @type array 0027 */ 0028 protected $context = array(); 0029 0030 /** 0031 * to test default's type. 0032 * @type HTMLPurifier_VarParser 0033 */ 0034 protected $parser; 0035 0036 public function __construct() 0037 { 0038 $this->parser = new HTMLPurifier_VarParser(); 0039 } 0040 0041 /** 0042 * Validates a fully-formed interchange object. 0043 * @param HTMLPurifier_ConfigSchema_Interchange $interchange 0044 * @return bool 0045 */ 0046 public function validate($interchange) 0047 { 0048 $this->interchange = $interchange; 0049 $this->aliases = array(); 0050 // PHP is a bit lax with integer <=> string conversions in 0051 // arrays, so we don't use the identical !== comparison 0052 foreach ($interchange->directives as $i => $directive) { 0053 $id = $directive->id->toString(); 0054 if ($i != $id) { 0055 $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); 0056 } 0057 $this->validateDirective($directive); 0058 } 0059 return true; 0060 } 0061 0062 /** 0063 * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. 0064 * @param HTMLPurifier_ConfigSchema_Interchange_Id $id 0065 */ 0066 public function validateId($id) 0067 { 0068 $id_string = $id->toString(); 0069 $this->context[] = "id '$id_string'"; 0070 if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { 0071 // handled by InterchangeBuilder 0072 $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); 0073 } 0074 // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) 0075 // we probably should check that it has at least one namespace 0076 $this->with($id, 'key') 0077 ->assertNotEmpty() 0078 ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder 0079 array_pop($this->context); 0080 } 0081 0082 /** 0083 * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. 0084 * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d 0085 */ 0086 public function validateDirective($d) 0087 { 0088 $id = $d->id->toString(); 0089 $this->context[] = "directive '$id'"; 0090 $this->validateId($d->id); 0091 0092 $this->with($d, 'description') 0093 ->assertNotEmpty(); 0094 0095 // BEGIN - handled by InterchangeBuilder 0096 $this->with($d, 'type') 0097 ->assertNotEmpty(); 0098 $this->with($d, 'typeAllowsNull') 0099 ->assertIsBool(); 0100 try { 0101 // This also tests validity of $d->type 0102 $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); 0103 } catch (HTMLPurifier_VarParserException $e) { 0104 $this->error('default', 'had error: ' . $e->getMessage()); 0105 } 0106 // END - handled by InterchangeBuilder 0107 0108 if (!is_null($d->allowed) || !empty($d->valueAliases)) { 0109 // allowed and valueAliases require that we be dealing with 0110 // strings, so check for that early. 0111 $d_int = HTMLPurifier_VarParser::$types[$d->type]; 0112 if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { 0113 $this->error('type', 'must be a string type when used with allowed or value aliases'); 0114 } 0115 } 0116 0117 $this->validateDirectiveAllowed($d); 0118 $this->validateDirectiveValueAliases($d); 0119 $this->validateDirectiveAliases($d); 0120 0121 array_pop($this->context); 0122 } 0123 0124 /** 0125 * Extra validation if $allowed member variable of 0126 * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. 0127 * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d 0128 */ 0129 public function validateDirectiveAllowed($d) 0130 { 0131 if (is_null($d->allowed)) { 0132 return; 0133 } 0134 $this->with($d, 'allowed') 0135 ->assertNotEmpty() 0136 ->assertIsLookup(); // handled by InterchangeBuilder 0137 if (is_string($d->default) && !isset($d->allowed[$d->default])) { 0138 $this->error('default', 'must be an allowed value'); 0139 } 0140 $this->context[] = 'allowed'; 0141 foreach ($d->allowed as $val => $x) { 0142 if (!is_string($val)) { 0143 $this->error("value $val", 'must be a string'); 0144 } 0145 } 0146 array_pop($this->context); 0147 } 0148 0149 /** 0150 * Extra validation if $valueAliases member variable of 0151 * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. 0152 * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d 0153 */ 0154 public function validateDirectiveValueAliases($d) 0155 { 0156 if (is_null($d->valueAliases)) { 0157 return; 0158 } 0159 $this->with($d, 'valueAliases') 0160 ->assertIsArray(); // handled by InterchangeBuilder 0161 $this->context[] = 'valueAliases'; 0162 foreach ($d->valueAliases as $alias => $real) { 0163 if (!is_string($alias)) { 0164 $this->error("alias $alias", 'must be a string'); 0165 } 0166 if (!is_string($real)) { 0167 $this->error("alias target $real from alias '$alias'", 'must be a string'); 0168 } 0169 if ($alias === $real) { 0170 $this->error("alias '$alias'", "must not be an alias to itself"); 0171 } 0172 } 0173 if (!is_null($d->allowed)) { 0174 foreach ($d->valueAliases as $alias => $real) { 0175 if (isset($d->allowed[$alias])) { 0176 $this->error("alias '$alias'", 'must not be an allowed value'); 0177 } elseif (!isset($d->allowed[$real])) { 0178 $this->error("alias '$alias'", 'must be an alias to an allowed value'); 0179 } 0180 } 0181 } 0182 array_pop($this->context); 0183 } 0184 0185 /** 0186 * Extra validation if $aliases member variable of 0187 * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. 0188 * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d 0189 */ 0190 public function validateDirectiveAliases($d) 0191 { 0192 $this->with($d, 'aliases') 0193 ->assertIsArray(); // handled by InterchangeBuilder 0194 $this->context[] = 'aliases'; 0195 foreach ($d->aliases as $alias) { 0196 $this->validateId($alias); 0197 $s = $alias->toString(); 0198 if (isset($this->interchange->directives[$s])) { 0199 $this->error("alias '$s'", 'collides with another directive'); 0200 } 0201 if (isset($this->aliases[$s])) { 0202 $other_directive = $this->aliases[$s]; 0203 $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); 0204 } 0205 $this->aliases[$s] = $d->id->toString(); 0206 } 0207 array_pop($this->context); 0208 } 0209 0210 // protected helper functions 0211 0212 /** 0213 * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom 0214 * for validating simple member variables of objects. 0215 * @param $obj 0216 * @param $member 0217 * @return HTMLPurifier_ConfigSchema_ValidatorAtom 0218 */ 0219 protected function with($obj, $member) 0220 { 0221 return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); 0222 } 0223 0224 /** 0225 * Emits an error, providing helpful context. 0226 * @throws HTMLPurifier_ConfigSchema_Exception 0227 */ 0228 protected function error($target, $msg) 0229 { 0230 if ($target !== false) { 0231 $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); 0232 } else { 0233 $prefix = ucfirst($this->getFormattedContext()); 0234 } 0235 throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); 0236 } 0237 0238 /** 0239 * Returns a formatted context string. 0240 * @return string 0241 */ 0242 protected function getFormattedContext() 0243 { 0244 return implode(' in ', array_reverse($this->context)); 0245 } 0246 } 0247 0248 // vim: et sw=4 sts=4