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