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