1 : <?php
2 :
3 : /**
4 : * Configuration object that triggers customizable behavior.
5 : *
6 : * @warning This class is strongly defined: that means that the class
7 : * will fail if an undefined directive is retrieved or set.
8 : *
9 : * @note Many classes that could (although many times don't) use the
10 : * configuration object make it a mandatory parameter. This is
11 : * because a configuration object should always be forwarded,
12 : * otherwise, you run the risk of missing a parameter and then
13 : * being stumped when a configuration directive doesn't work.
14 : *
15 : * @todo Reconsider some of the public member variables
16 : */
17 : class HTMLPurifier_Config
18 1 : {
19 :
20 : /**
21 : * HTML Purifier's version
22 : */
23 : public $version = '3.1.1';
24 :
25 : /**
26 : * Bool indicator whether or not to automatically finalize
27 : * the object if a read operation is done
28 : */
29 : public $autoFinalize = true;
30 :
31 : // protected member variables
32 :
33 : /**
34 : * Namespace indexed array of serials for specific namespaces (see
35 : * getSerial() for more info).
36 : */
37 : protected $serials = array();
38 :
39 : /**
40 : * Serial for entire configuration object
41 : */
42 : protected $serial;
43 :
44 : /**
45 : * Two-level associative array of configuration directives
46 : */
47 : protected $conf;
48 :
49 : /**
50 : * Parser for variables
51 : */
52 : protected $parser;
53 :
54 : /**
55 : * Reference HTMLPurifier_ConfigSchema for value checking
56 : * @note This is public for introspective purposes. Please don't
57 : * abuse!
58 : */
59 : public $def;
60 :
61 : /**
62 : * Indexed array of definitions
63 : */
64 : protected $definitions;
65 :
66 : /**
67 : * Bool indicator whether or not config is finalized
68 : */
69 : protected $finalized = false;
70 :
71 : /**
72 : * @param $definition HTMLPurifier_ConfigSchema that defines what directives
73 : * are allowed.
74 : */
75 : public function __construct($definition) {
76 2 : $this->conf = $definition->defaults; // set up, copy in defaults
77 2 : $this->def = $definition; // keep a copy around for checking
78 2 : $this->parser = new HTMLPurifier_VarParser_Flexible();
79 2 : }
80 :
81 : /**
82 : * Convenience constructor that creates a config object based on a mixed var
83 : * @param mixed $config Variable that defines the state of the config
84 : * object. Can be: a HTMLPurifier_Config() object,
85 : * an array of directives based on loadArray(),
86 : * or a string filename of an ini file.
87 : * @param HTMLPurifier_ConfigSchema Schema object
88 : * @return Configured HTMLPurifier_Config object
89 : */
90 : public static function create($config, $schema = null) {
91 2 : if ($config instanceof HTMLPurifier_Config) {
92 : // pass-through
93 2 : return $config;
94 : }
95 0 : if (!$schema) {
96 0 : $ret = HTMLPurifier_Config::createDefault();
97 0 : } else {
98 0 : $ret = new HTMLPurifier_Config($schema);
99 : }
100 0 : if (is_string($config)) $ret->loadIni($config);
101 0 : elseif (is_array($config)) $ret->loadArray($config);
102 0 : return $ret;
103 : }
104 :
105 : /**
106 : * Convenience constructor that creates a default configuration object.
107 : * @return Default HTMLPurifier_Config object.
108 : */
109 : public static function createDefault() {
110 2 : $definition = HTMLPurifier_ConfigSchema::instance();
111 2 : $config = new HTMLPurifier_Config($definition);
112 2 : return $config;
113 : }
114 :
115 : /**
116 : * Retreives a value from the configuration.
117 : * @param $namespace String namespace
118 : * @param $key String key
119 : */
120 : public function get($namespace, $key) {
121 2 : if (!$this->finalized && $this->autoFinalize) $this->finalize();
122 2 : if (!isset($this->def->info[$namespace][$key])) {
123 : // can't add % due to SimpleTest bug
124 0 : trigger_error('Cannot retrieve value of undefined directive ' . htmlspecialchars("$namespace.$key"),
125 0 : E_USER_WARNING);
126 0 : return;
127 : }
128 2 : if (isset($this->def->info[$namespace][$key]->isAlias)) {
129 0 : $d = $this->def->info[$namespace][$key];
130 0 : trigger_error('Cannot get value from aliased directive, use real name ' . $d->namespace . '.' . $d->name,
131 0 : E_USER_ERROR);
132 0 : return;
133 : }
134 2 : return $this->conf[$namespace][$key];
135 : }
136 :
137 : /**
138 : * Retreives an array of directives to values from a given namespace
139 : * @param $namespace String namespace
140 : */
141 : public function getBatch($namespace) {
142 2 : if (!$this->finalized && $this->autoFinalize) $this->finalize();
143 2 : if (!isset($this->def->info[$namespace])) {
144 0 : trigger_error('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
145 0 : E_USER_WARNING);
146 0 : return;
147 : }
148 2 : return $this->conf[$namespace];
149 : }
150 :
151 : /**
152 : * Returns a md5 signature of a segment of the configuration object
153 : * that uniquely identifies that particular configuration
154 : * @note Revision is handled specially and is removed from the batch
155 : * before processing!
156 : * @param $namespace Namespace to get serial for
157 : */
158 : public function getBatchSerial($namespace) {
159 2 : if (empty($this->serials[$namespace])) {
160 2 : $batch = $this->getBatch($namespace);
161 2 : unset($batch['DefinitionRev']);
162 2 : $this->serials[$namespace] = md5(serialize($batch));
163 2 : }
164 2 : return $this->serials[$namespace];
165 : }
166 :
167 : /**
168 : * Returns a md5 signature for the entire configuration object
169 : * that uniquely identifies that particular configuration
170 : */
171 : public function getSerial() {
172 0 : if (empty($this->serial)) {
173 0 : $this->serial = md5(serialize($this->getAll()));
174 0 : }
175 0 : return $this->serial;
176 : }
177 :
178 : /**
179 : * Retrieves all directives, organized by namespace
180 : */
181 : public function getAll() {
182 0 : if (!$this->finalized && $this->autoFinalize) $this->finalize();
183 0 : return $this->conf;
184 : }
185 :
186 : /**
187 : * Sets a value to configuration.
188 : * @param $namespace String namespace
189 : * @param $key String key
190 : * @param $value Mixed value
191 : */
192 : public function set($namespace, $key, $value, $from_alias = false) {
193 2 : if ($this->isFinalized('Cannot set directive after finalization')) return;
194 2 : if (!isset($this->def->info[$namespace][$key])) {
195 0 : trigger_error('Cannot set undefined directive ' . htmlspecialchars("$namespace.$key") . ' to value',
196 0 : E_USER_WARNING);
197 0 : return;
198 : }
199 2 : $def = $this->def->info[$namespace][$key];
200 :
201 2 : if (isset($def->isAlias)) {
202 0 : if ($from_alias) {
203 0 : trigger_error('Double-aliases not allowed, please fix '.
204 0 : 'ConfigSchema bug with' . "$namespace.$key", E_USER_ERROR);
205 0 : return;
206 : }
207 0 : $this->set($new_ns = $def->namespace,
208 0 : $new_dir = $def->name,
209 0 : $value, true);
210 0 : trigger_error("$namespace.$key is an alias, preferred directive name is $new_ns.$new_dir", E_USER_NOTICE);
211 0 : return;
212 : }
213 :
214 : // Raw type might be negative when using the fully optimized form
215 : // of stdclass, which indicates allow_null == true
216 2 : $rtype = is_int($def) ? $def : $def->type;
217 2 : if ($rtype < 0) {
218 2 : $type = -$rtype;
219 2 : $allow_null = true;
220 2 : } else {
221 2 : $type = $rtype;
222 2 : $allow_null = isset($def->allow_null);
223 : }
224 :
225 : try {
226 2 : $value = $this->parser->parse($value, $type, $allow_null);
227 2 : } catch (HTMLPurifier_VarParserException $e) {
228 : trigger_error('Value for ' . "$namespace.$key" . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
229 : return;
230 : }
231 2 : if (is_string($value) && is_object($def)) {
232 : // resolve value alias if defined
233 0 : if (isset($def->aliases[$value])) {
234 0 : $value = $def->aliases[$value];
235 0 : }
236 : // check to see if the value is allowed
237 0 : if (isset($def->allowed) && !isset($def->allowed[$value])) {
238 0 : trigger_error('Value not supported, valid values are: ' .
239 0 : $this->_listify($def->allowed), E_USER_WARNING);
240 0 : return;
241 : }
242 0 : }
243 2 : $this->conf[$namespace][$key] = $value;
244 :
245 : // reset definitions if the directives they depend on changed
246 : // this is a very costly process, so it's discouraged
247 : // with finalization
248 2 : if ($namespace == 'HTML' || $namespace == 'CSS') {
249 0 : $this->definitions[$namespace] = null;
250 0 : }
251 :
252 2 : $this->serials[$namespace] = false;
253 2 : }
254 :
255 : /**
256 : * Convenience function for error reporting
257 : */
258 : private function _listify($lookup) {
259 0 : $list = array();
260 0 : foreach ($lookup as $name => $b) $list[] = $name;
261 0 : return implode(', ', $list);
262 : }
263 :
264 : /**
265 : * Retrieves object reference to the HTML definition.
266 : * @param $raw Return a copy that has not been setup yet. Must be
267 : * called before it's been setup, otherwise won't work.
268 : */
269 : public function getHTMLDefinition($raw = false) {
270 2 : return $this->getDefinition('HTML', $raw);
271 : }
272 :
273 : /**
274 : * Retrieves object reference to the CSS definition
275 : * @param $raw Return a copy that has not been setup yet. Must be
276 : * called before it's been setup, otherwise won't work.
277 : */
278 : public function getCSSDefinition($raw = false) {
279 2 : return $this->getDefinition('CSS', $raw);
280 : }
281 :
282 : /**
283 : * Retrieves a definition
284 : * @param $type Type of definition: HTML, CSS, etc
285 : * @param $raw Whether or not definition should be returned raw
286 : */
287 : public function getDefinition($type, $raw = false) {
288 2 : if (!$this->finalized && $this->autoFinalize) $this->finalize();
289 2 : $factory = HTMLPurifier_DefinitionCacheFactory::instance();
290 2 : $cache = $factory->create($type, $this);
291 2 : if (!$raw) {
292 : // see if we can quickly supply a definition
293 2 : if (!empty($this->definitions[$type])) {
294 2 : if (!$this->definitions[$type]->setup) {
295 0 : $this->definitions[$type]->setup($this);
296 0 : $cache->set($this->definitions[$type], $this);
297 0 : }
298 2 : return $this->definitions[$type];
299 : }
300 : // memory check missed, try cache
301 2 : $this->definitions[$type] = $cache->get($this);
302 2 : if ($this->definitions[$type]) {
303 : // definition in cache, return it
304 2 : return $this->definitions[$type];
305 : }
306 0 : } elseif (
307 0 : !empty($this->definitions[$type]) &&
308 0 : !$this->definitions[$type]->setup
309 0 : ) {
310 : // raw requested, raw in memory, quick return
311 0 : return $this->definitions[$type];
312 : }
313 : // quick checks failed, let's create the object
314 0 : if ($type == 'HTML') {
315 0 : $this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
316 0 : } elseif ($type == 'CSS') {
317 0 : $this->definitions[$type] = new HTMLPurifier_CSSDefinition();
318 0 : } elseif ($type == 'URI') {
319 0 : $this->definitions[$type] = new HTMLPurifier_URIDefinition();
320 0 : } else {
321 0 : throw new HTMLPurifier_Exception("Definition of $type type not supported");
322 : }
323 : // quick abort if raw
324 0 : if ($raw) {
325 0 : if (is_null($this->get($type, 'DefinitionID'))) {
326 : // fatally error out if definition ID not set
327 0 : throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
328 : }
329 0 : return $this->definitions[$type];
330 : }
331 : // set it up
332 0 : $this->definitions[$type]->setup($this);
333 : // save in cache
334 0 : $cache->set($this->definitions[$type], $this);
335 0 : return $this->definitions[$type];
336 : }
337 :
338 : /**
339 : * Loads configuration values from an array with the following structure:
340 : * Namespace.Directive => Value
341 : * @param $config_array Configuration associative array
342 : */
343 : public function loadArray($config_array) {
344 0 : if ($this->isFinalized('Cannot load directives after finalization')) return;
345 0 : foreach ($config_array as $key => $value) {
346 0 : $key = str_replace('_', '.', $key);
347 0 : if (strpos($key, '.') !== false) {
348 : // condensed form
349 0 : list($namespace, $directive) = explode('.', $key);
350 0 : $this->set($namespace, $directive, $value);
351 0 : } else {
352 0 : $namespace = $key;
353 0 : $namespace_values = $value;
354 0 : foreach ($namespace_values as $directive => $value) {
355 0 : $this->set($namespace, $directive, $value);
356 0 : }
357 : }
358 0 : }
359 0 : }
360 :
361 : /**
362 : * Returns a list of array(namespace, directive) for all directives
363 : * that are allowed in a web-form context as per an allowed
364 : * namespaces/directives list.
365 : * @param $allowed List of allowed namespaces/directives
366 : */
367 : public static function getAllowedDirectivesForForm($allowed, $schema = null) {
368 0 : if (!$schema) {
369 0 : $schema = HTMLPurifier_ConfigSchema::instance();
370 0 : }
371 0 : if ($allowed !== true) {
372 0 : if (is_string($allowed)) $allowed = array($allowed);
373 0 : $allowed_ns = array();
374 0 : $allowed_directives = array();
375 0 : $blacklisted_directives = array();
376 0 : foreach ($allowed as $ns_or_directive) {
377 0 : if (strpos($ns_or_directive, '.') !== false) {
378 : // directive
379 0 : if ($ns_or_directive[0] == '-') {
380 0 : $blacklisted_directives[substr($ns_or_directive, 1)] = true;
381 0 : } else {
382 0 : $allowed_directives[$ns_or_directive] = true;
383 : }
384 0 : } else {
385 : // namespace
386 0 : $allowed_ns[$ns_or_directive] = true;
387 : }
388 0 : }
389 0 : }
390 0 : $ret = array();
391 0 : foreach ($schema->info as $ns => $keypairs) {
392 0 : foreach ($keypairs as $directive => $def) {
393 0 : if ($allowed !== true) {
394 0 : if (isset($blacklisted_directives["$ns.$directive"])) continue;
395 0 : if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
396 0 : }
397 0 : if (isset($def->isAlias)) continue;
398 0 : if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
399 0 : $ret[] = array($ns, $directive);
400 0 : }
401 0 : }
402 0 : return $ret;
403 : }
404 :
405 : /**
406 : * Loads configuration values from $_GET/$_POST that were posted
407 : * via ConfigForm
408 : * @param $array $_GET or $_POST array to import
409 : * @param $index Index/name that the config variables are in
410 : * @param $allowed List of allowed namespaces/directives
411 : * @param $mq_fix Boolean whether or not to enable magic quotes fix
412 : * @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy
413 : */
414 : public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
415 0 : $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
416 0 : $config = HTMLPurifier_Config::create($ret, $schema);
417 0 : return $config;
418 : }
419 :
420 : /**
421 : * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
422 : * @note Same parameters as loadArrayFromForm
423 : */
424 : public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
425 0 : $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
426 0 : $this->loadArray($ret);
427 0 : }
428 :
429 : /**
430 : * Prepares an array from a form into something usable for the more
431 : * strict parts of HTMLPurifier_Config
432 : */
433 : public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
434 0 : if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
435 0 : $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
436 :
437 0 : $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
438 0 : $ret = array();
439 0 : foreach ($allowed as $key) {
440 0 : list($ns, $directive) = $key;
441 0 : $skey = "$ns.$directive";
442 0 : if (!empty($array["Null_$skey"])) {
443 0 : $ret[$ns][$directive] = null;
444 0 : continue;
445 0 : }
446 0 : if (!isset($array[$skey])) continue;
447 0 : $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
448 0 : $ret[$ns][$directive] = $value;
449 0 : }
450 0 : return $ret;
451 : }
452 :
453 : /**
454 : * Loads configuration values from an ini file
455 : * @param $filename Name of ini file
456 : */
457 : public function loadIni($filename) {
458 0 : if ($this->isFinalized('Cannot load directives after finalization')) return;
459 0 : $array = parse_ini_file($filename, true);
460 0 : $this->loadArray($array);
461 0 : }
462 :
463 : /**
464 : * Checks whether or not the configuration object is finalized.
465 : * @param $error String error message, or false for no error
466 : */
467 : public function isFinalized($error = false) {
468 2 : if ($this->finalized && $error) {
469 0 : trigger_error($error, E_USER_ERROR);
470 0 : }
471 2 : return $this->finalized;
472 : }
473 :
474 : /**
475 : * Finalizes configuration only if auto finalize is on and not
476 : * already finalized
477 : */
478 : public function autoFinalize() {
479 0 : if (!$this->finalized && $this->autoFinalize) $this->finalize();
480 0 : }
481 :
482 : /**
483 : * Finalizes a configuration object, prohibiting further change
484 : */
485 : public function finalize() {
486 2 : $this->finalized = true;
487 2 : }
488 :
489 : }
490 :
491 :
492 :
|