Fix CSS URL innerHTML/cssText escaping bug.
[htmlpurifier.git] / tests / HTMLPurifier / ConfigTest.php
blob5ffe5f5405ce6c9d4c5223d6e9d1e2e04f19aac9
1 <?php
3 class HTMLPurifier_ConfigTest extends HTMLPurifier_Harness
6 protected $schema;
7 protected $oldFactory;
9 public function setUp() {
10 // set up a dummy schema object for testing
11 $this->schema = new HTMLPurifier_ConfigSchema();
14 // test functionality based on ConfigSchema
16 function testNormal() {
17 $this->schema->add('Element.Abbr', 'H', 'string', false);
18 $this->schema->add('Element.Name', 'hydrogen', 'istring', false);
19 $this->schema->add('Element.Number', 1, 'int', false);
20 $this->schema->add('Element.Mass', 1.00794, 'float', false);
21 $this->schema->add('Element.Radioactive', false, 'bool', false);
22 $this->schema->add('Element.Isotopes', array(1 => true, 2 => true, 3 => true), 'lookup', false);
23 $this->schema->add('Element.Traits', array('nonmetallic', 'odorless', 'flammable'), 'list', false);
24 $this->schema->add('Element.IsotopeNames', array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'), 'hash', false);
25 $this->schema->add('Element.Object', new stdClass(), 'mixed', false);
27 $config = new HTMLPurifier_Config($this->schema);
28 $config->autoFinalize = false;
29 $config->chatty = false;
31 // test default value retrieval
32 $this->assertIdentical($config->get('Element.Abbr'), 'H');
33 $this->assertIdentical($config->get('Element.Name'), 'hydrogen');
34 $this->assertIdentical($config->get('Element.Number'), 1);
35 $this->assertIdentical($config->get('Element.Mass'), 1.00794);
36 $this->assertIdentical($config->get('Element.Radioactive'), false);
37 $this->assertIdentical($config->get('Element.Isotopes'), array(1 => true, 2 => true, 3 => true));
38 $this->assertIdentical($config->get('Element.Traits'), array('nonmetallic', 'odorless', 'flammable'));
39 $this->assertIdentical($config->get('Element.IsotopeNames'), array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'));
40 $this->assertIdentical($config->get('Element.Object'), new stdClass());
42 // test setting values
43 $config->set('Element.Abbr', 'Pu');
44 $config->set('Element.Name', 'PLUTONIUM'); // test decaps
45 $config->set('Element.Number', '94'); // test parsing
46 $config->set('Element.Mass', '244.'); // test parsing
47 $config->set('Element.Radioactive', true);
48 $config->set('Element.Isotopes', array(238, 239)); // test inversion
49 $config->set('Element.Traits', 'nuclear, heavy, actinide'); // test parsing
50 $config->set('Element.IsotopeNames', array(238 => 'Plutonium-238', 239 => 'Plutonium-239'));
51 $config->set('Element.Object', false); // unmodeled
53 $this->expectError('Cannot set undefined directive Element.Metal to value');
54 $config->set('Element.Metal', true);
56 $this->expectError('Value for Element.Radioactive is of invalid type, should be bool');
57 $config->set('Element.Radioactive', 'very');
59 // test value retrieval
60 $this->assertIdentical($config->get('Element.Abbr'), 'Pu');
61 $this->assertIdentical($config->get('Element.Name'), 'plutonium');
62 $this->assertIdentical($config->get('Element.Number'), 94);
63 $this->assertIdentical($config->get('Element.Mass'), 244.);
64 $this->assertIdentical($config->get('Element.Radioactive'), true);
65 $this->assertIdentical($config->get('Element.Isotopes'), array(238 => true, 239 => true));
66 $this->assertIdentical($config->get('Element.Traits'), array('nuclear', 'heavy', 'actinide'));
67 $this->assertIdentical($config->get('Element.IsotopeNames'), array(238 => 'Plutonium-238', 239 => 'Plutonium-239'));
68 $this->assertIdentical($config->get('Element.Object'), false);
70 $this->expectError('Cannot retrieve value of undefined directive Element.Metal');
71 $config->get('Element.Metal');
75 function testEnumerated() {
77 // case sensitive
78 $this->schema->add('Instrument.Manufacturer', 'Yamaha', 'string', false);
79 $this->schema->addAllowedValues('Instrument.Manufacturer', array(
80 'Yamaha' => true, 'Conn-Selmer' => true, 'Vandoren' => true,
81 'Laubin' => true, 'Buffet' => true, 'other' => true));
82 $this->schema->addValueAliases('Instrument.Manufacturer', array(
83 'Selmer' => 'Conn-Selmer'));
85 // case insensitive
86 $this->schema->add('Instrument.Family', 'woodwind', 'istring', false);
87 $this->schema->addAllowedValues('Instrument.Family', array(
88 'brass' => true, 'woodwind' => true, 'percussion' => true,
89 'string' => true, 'keyboard' => true, 'electronic' => true));
90 $this->schema->addValueAliases('Instrument.Family', array(
91 'synth' => 'electronic'));
93 $config = new HTMLPurifier_Config($this->schema);
94 $config->autoFinalize = false;
95 $config->chatty = false;
97 // case sensitive
99 $config->set('Instrument.Manufacturer', 'Vandoren');
100 $this->assertIdentical($config->get('Instrument.Manufacturer'), 'Vandoren');
102 $config->set('Instrument.Manufacturer', 'Selmer');
103 $this->assertIdentical($config->get('Instrument.Manufacturer'), 'Conn-Selmer');
105 $this->expectError('Value not supported, valid values are: Yamaha, Conn-Selmer, Vandoren, Laubin, Buffet, other');
106 $config->set('Instrument.Manufacturer', 'buffet');
108 // case insensitive
110 $config->set('Instrument.Family', 'brass');
111 $this->assertIdentical($config->get('Instrument.Family'), 'brass');
113 $config->set('Instrument.Family', 'PERCUSSION');
114 $this->assertIdentical($config->get('Instrument.Family'), 'percussion');
116 $config->set('Instrument.Family', 'synth');
117 $this->assertIdentical($config->get('Instrument.Family'), 'electronic');
119 $config->set('Instrument.Family', 'Synth');
120 $this->assertIdentical($config->get('Instrument.Family'), 'electronic');
124 function testNull() {
126 $this->schema->add('ReportCard.English', null, 'string', true);
127 $this->schema->add('ReportCard.Absences', 0, 'int', false);
129 $config = new HTMLPurifier_Config($this->schema);
130 $config->autoFinalize = false;
131 $config->chatty = false;
133 $config->set('ReportCard.English', 'B-');
134 $this->assertIdentical($config->get('ReportCard.English'), 'B-');
136 $config->set('ReportCard.English', null); // not yet graded
137 $this->assertIdentical($config->get('ReportCard.English'), null);
139 // error
140 $this->expectError('Value for ReportCard.Absences is of invalid type, should be int');
141 $config->set('ReportCard.Absences', null);
145 function testAliases() {
147 $this->schema->add('Home.Rug', 3, 'int', false);
148 $this->schema->addAlias('Home.Carpet', 'Home.Rug');
150 $config = new HTMLPurifier_Config($this->schema);
151 $config->autoFinalize = false;
152 $config->chatty = false;
154 $this->assertIdentical($config->get('Home.Rug'), 3);
156 $this->expectError('Cannot get value from aliased directive, use real name Home.Rug');
157 $config->get('Home.Carpet');
159 $this->expectError('Home.Carpet is an alias, preferred directive name is Home.Rug');
160 $config->set('Home.Carpet', 999);
161 $this->assertIdentical($config->get('Home.Rug'), 999);
165 // test functionality based on method
167 function test_getBatch() {
169 $this->schema->add('Variables.TangentialAcceleration', 'a_tan', 'string', false);
170 $this->schema->add('Variables.AngularAcceleration', 'alpha', 'string', false);
172 $config = new HTMLPurifier_Config($this->schema);
173 $config->autoFinalize = false;
174 $config->chatty = false;
176 // grab a namespace
177 $this->assertIdentical(
178 $config->getBatch('Variables'),
179 array(
180 'TangentialAcceleration' => 'a_tan',
181 'AngularAcceleration' => 'alpha'
185 // grab a non-existant namespace
186 $this->expectError('Cannot retrieve undefined namespace Constants');
187 $config->getBatch('Constants');
191 function test_loadIni() {
193 $this->schema->add('Shortcut.Copy', 'c', 'istring', false);
194 $this->schema->add('Shortcut.Paste', 'v', 'istring', false);
195 $this->schema->add('Shortcut.Cut', 'x', 'istring', false);
197 $config = new HTMLPurifier_Config($this->schema);
198 $config->autoFinalize = false;
200 $config->loadIni(dirname(__FILE__) . '/ConfigTest-loadIni.ini');
202 $this->assertIdentical($config->get('Shortcut.Copy'), 'q');
203 $this->assertIdentical($config->get('Shortcut.Paste'), 'p');
204 $this->assertIdentical($config->get('Shortcut.Cut'), 't');
208 function test_getHTMLDefinition() {
210 // we actually want to use the old copy, because the definition
211 // generation routines have dependencies on configuration values
213 $config = HTMLPurifier_Config::createDefault();
214 $config->set('HTML.Doctype', 'XHTML 1.0 Strict');
215 $config->autoFinalize = false;
217 $def = $config->getCSSDefinition();
218 $this->assertIsA($def, 'HTMLPurifier_CSSDefinition');
220 $def = $config->getHTMLDefinition();
221 $def2 = $config->getHTMLDefinition();
222 $this->assertIsA($def, 'HTMLPurifier_HTMLDefinition');
223 $this->assertSame($def, $def2);
224 $this->assertTrue($def->setup);
226 $old_def = clone $def2;
228 $config->set('HTML.Doctype', 'HTML 4.01 Transitional');
229 $def = $config->getHTMLDefinition();
230 $this->assertIsA($def, 'HTMLPurifier_HTMLDefinition');
231 $this->assertNotEqual($def, $old_def);
232 $this->assertTrue($def->setup);
236 function test_getHTMLDefinition_deprecatedRawError() {
237 $config = HTMLPurifier_Config::createDefault();
238 $config->chatty = false;
239 // test deprecated retrieval of raw definition
240 $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->test_getHTMLDefinition()');
241 $config->set('HTML.DefinitionRev', 3);
242 $this->expectError("Useless DefinitionID declaration");
243 $def = $config->getHTMLDefinition(true);
244 $this->assertEqual(false, $def->setup);
246 // auto initialization
247 $config->getHTMLDefinition();
248 $this->assertTrue($def->setup);
251 function test_getHTMLDefinition_optimizedRawError() {
252 $this->expectException(new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"));
253 $config = HTMLPurifier_Config::createDefault();
254 $config->getHTMLDefinition(false, true);
257 function test_getHTMLDefinition_rawAfterSetupError() {
258 $this->expectException(new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup"));
259 $config = HTMLPurifier_Config::createDefault();
260 $config->chatty = false;
261 $config->getHTMLDefinition();
262 $config->getHTMLDefinition(true);
265 function test_getHTMLDefinition_inconsistentOptimizedError() {
266 $this->expectError("Useless DefinitionID declaration");
267 $this->expectException(new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals"));
268 $config = HTMLPurifier_Config::create(array('HTML.DefinitionID' => 'HTMLPurifier_ConfigTest->test_getHTMLDefinition_inconsistentOptimizedError'));
269 $config->chatty = false;
270 $config->getHTMLDefinition(true, false);
271 $config->getHTMLDefinition(true, true);
274 function test_getHTMLDefinition_inconsistentOptimizedError2() {
275 $this->expectException(new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals"));
276 $config = HTMLPurifier_Config::create(array('HTML.DefinitionID' => 'HTMLPurifier_ConfigTest->test_getHTMLDefinition_inconsistentOptimizedError2'));
277 $config->chatty = false;
278 $config->getHTMLDefinition(true, true);
279 $config->getHTMLDefinition(true, false);
282 function test_getHTMLDefinition_rawError() {
283 $config = HTMLPurifier_Config::createDefault();
284 $this->expectException(new HTMLPurifier_Exception('Cannot retrieve raw version without specifying %HTML.DefinitionID'));
285 $def = $config->getHTMLDefinition(true, true);
288 function test_getCSSDefinition() {
289 $config = HTMLPurifier_Config::createDefault();
290 $def = $config->getCSSDefinition();
291 $this->assertIsA($def, 'HTMLPurifier_CSSDefinition');
294 function test_getDefinition() {
295 $this->schema->add('Cache.DefinitionImpl', null, 'string', true);
296 $config = new HTMLPurifier_Config($this->schema);
297 $this->expectException(new HTMLPurifier_Exception("Definition of Crust type not supported"));
298 $config->getDefinition('Crust');
301 function test_loadArray() {
302 // setup a few dummy namespaces/directives for our testing
303 $this->schema->add('Zoo.Aadvark', 0, 'int', false);
304 $this->schema->add('Zoo.Boar', 0, 'int', false);
305 $this->schema->add('Zoo.Camel', 0, 'int', false);
306 $this->schema->add('Zoo.Others', array(), 'list', false);
308 $config_manual = new HTMLPurifier_Config($this->schema);
309 $config_loadabbr = new HTMLPurifier_Config($this->schema);
310 $config_loadfull = new HTMLPurifier_Config($this->schema);
312 $config_manual->set('Zoo.Aadvark', 3);
313 $config_manual->set('Zoo.Boar', 5);
314 $config_manual->set('Zoo.Camel', 2000); // that's a lotta camels!
315 $config_manual->set('Zoo.Others', array('Peacock', 'Dodo')); // wtf!
317 // condensed form
318 $config_loadabbr->loadArray(array(
319 'Zoo.Aadvark' => 3,
320 'Zoo.Boar' => 5,
321 'Zoo.Camel' => 2000,
322 'Zoo.Others' => array('Peacock', 'Dodo')
325 // fully expanded form
326 $config_loadfull->loadArray(array(
327 'Zoo' => array(
328 'Aadvark' => 3,
329 'Boar' => 5,
330 'Camel' => 2000,
331 'Others' => array('Peacock', 'Dodo')
335 $this->assertIdentical($config_manual, $config_loadabbr);
336 $this->assertIdentical($config_manual, $config_loadfull);
340 function test_create() {
342 $this->schema->add('Cake.Sprinkles', 666, 'int', false);
343 $this->schema->add('Cake.Flavor', 'vanilla', 'string', false);
345 $config = new HTMLPurifier_Config($this->schema);
346 $config->set('Cake.Sprinkles', 42);
348 // test flat pass-through
349 $created_config = HTMLPurifier_Config::create($config, $this->schema);
350 $this->assertIdentical($config, $created_config);
352 // test loadArray
353 $created_config = HTMLPurifier_Config::create(array('Cake.Sprinkles' => 42), $this->schema);
354 $this->assertIdentical($config, $created_config);
356 // test loadIni
357 $created_config = HTMLPurifier_Config::create(dirname(__FILE__) . '/ConfigTest-create.ini', $this->schema);
358 $this->assertIdentical($config, $created_config);
362 function test_finalize() {
364 // test finalization
366 $this->schema->add('Poem.Meter', 'iambic', 'string', false);
368 $config = new HTMLPurifier_Config($this->schema);
369 $config->autoFinalize = false;
370 $config->chatty = false;
372 $config->set('Poem.Meter', 'irregular');
374 $config->finalize();
376 $this->expectError('Cannot set directive after finalization');
377 $config->set('Poem.Meter', 'vedic');
379 $this->expectError('Cannot load directives after finalization');
380 $config->loadArray(array('Poem.Meter' => 'octosyllable'));
382 $this->expectError('Cannot load directives after finalization');
383 $config->loadIni(dirname(__FILE__) . '/ConfigTest-finalize.ini');
387 function test_loadArrayFromForm() {
389 $this->schema->add('Pancake.Mix', 'buttermilk', 'string', false);
390 $this->schema->add('Pancake.Served', true, 'bool', false);
391 $this->schema->add('Toppings.Syrup', true, 'bool', false);
392 $this->schema->add('Toppings.Flavor', 'maple', 'string', false);
393 $this->schema->add('Toppings.Strawberries', 3, 'int', false);
394 $this->schema->add('Toppings.Calories', 2000, 'int', true);
395 $this->schema->add('Toppings.DefinitionID', null, 'string', true);
396 $this->schema->add('Toppings.DefinitionRev', 1, 'int', false);
397 $this->schema->add('Toppings.Protected', 1, 'int', false);
399 $get = array(
400 'breakfast' => array(
401 'Pancake.Mix' => 'nasty',
402 'Pancake.Served' => '0',
403 'Toppings.Syrup' => '0',
404 'Toppings.Flavor' => "juice",
405 'Toppings.Strawberries' => '999',
406 'Toppings.Calories' => '',
407 'Null_Toppings.Calories' => '1',
408 'Toppings.DefinitionID' => '<argh>',
409 'Toppings.DefinitionRev' => '65',
410 'Toppings.Protected' => '4',
414 $config_expect = HTMLPurifier_Config::create(array(
415 'Pancake.Served' => false,
416 'Toppings.Syrup' => false,
417 'Toppings.Flavor' => "juice",
418 'Toppings.Strawberries' => 999,
419 'Toppings.Calories' => null
420 ), $this->schema);
422 $config_result = HTMLPurifier_Config::loadArrayFromForm(
423 $get, 'breakfast',
424 array('Pancake.Served', 'Toppings', '-Toppings.Protected'),
425 false, // mq fix
426 $this->schema
429 $this->assertEqual($config_expect, $config_result);
432 MAGIC QUOTES NOT TESTED!!!
434 $get = array(
435 'breakfast' => array(
436 'Pancake.Mix' => 'n\\asty'
439 $config_expect = HTMLPurifier_Config::create(array(
440 'Pancake.Mix' => 'n\\asty'
442 $config_result = HTMLPurifier_Config::loadArrayFromForm($get, 'breakfast', true, false);
443 $this->assertEqual($config_expect, $config_result);
447 function test_getAllowedDirectivesForForm() {
448 $this->schema->add('Unused.Unused', 'Foobar', 'string', false);
449 $this->schema->add('Partial.Allowed', true, 'bool', false);
450 $this->schema->add('Partial.Unused', 'Foobar', 'string', false);
451 $this->schema->add('All.Allowed', true, 'bool', false);
452 $this->schema->add('All.Blacklisted', 'Foobar', 'string', false); // explicitly blacklisted
453 $this->schema->add('All.DefinitionID', 'Foobar', 'string', true); // auto-blacklisted
454 $this->schema->add('All.DefinitionRev', 2, 'int', false); // auto-blacklisted
456 $input = array('Partial.Allowed', 'All', '-All.Blacklisted');
457 $output = HTMLPurifier_Config::getAllowedDirectivesForForm($input, $this->schema);
458 $expect = array(
459 array('Partial', 'Allowed'),
460 array('All', 'Allowed')
463 $this->assertEqual($output, $expect);
467 function testDeprecatedAPI() {
468 $this->schema->add('Foo.Bar', 2, 'int', false);
469 $config = new HTMLPurifier_Config($this->schema);
470 $config->chatty = false;
471 $this->expectError('Using deprecated API: use $config->set(\'Foo.Bar\', ...) instead');
472 $config->set('Foo', 'Bar', 4);
473 $this->expectError('Using deprecated API: use $config->get(\'Foo.Bar\') instead');
474 $this->assertIdentical($config->get('Foo', 'Bar'), 4);
477 function testInherit() {
478 $this->schema->add('Phantom.Masked', 25, 'int', false);
479 $this->schema->add('Phantom.Unmasked', 89, 'int', false);
480 $this->schema->add('Phantom.Latemasked', 11, 'int', false);
481 $config = new HTMLPurifier_Config($this->schema);
482 $config->set('Phantom.Masked', 800);
483 $subconfig = HTMLPurifier_Config::inherit($config);
484 $config->set('Phantom.Latemasked', 100, 'int', false);
485 $this->assertIdentical($subconfig->get('Phantom.Masked'), 800);
486 $this->assertIdentical($subconfig->get('Phantom.Unmasked'), 89);
487 $this->assertIdentical($subconfig->get('Phantom.Latemasked'), 100);
490 function testSerialize() {
491 $config = HTMLPurifier_Config::createDefault();
492 $config->set('HTML.Allowed', 'a');
493 $config2 = unserialize($config->serialize());
494 $this->assertIdentical($config, $config2);
497 function testDefinitionCachingNothing() {
498 list($mock, $config) = $this->setupCacheMock('HTML');
499 // should not touch the cache
500 $mock->expectNever('get');
501 $mock->expectNever('add');
502 $mock->expectNever('set');
503 $config->getDefinition('HTML', true);
504 $config->getDefinition('HTML', true);
505 $config->getDefinition('HTML');
506 $this->teardownCacheMock();
509 function testDefinitionCachingOptimized() {
510 list($mock, $config) = $this->setupCacheMock('HTML');
511 $mock->expectNever('set');
512 $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->testDefinitionCachingOptimized');
513 $mock->expectOnce('get');
514 $mock->setReturnValue('get', null);
515 $this->assertTrue($config->maybeGetRawHTMLDefinition());
516 $this->assertTrue($config->maybeGetRawHTMLDefinition());
517 $mock->expectOnce('add');
518 $config->getDefinition('HTML');
519 $this->teardownCacheMock();
522 function testDefinitionCachingOptimizedHit() {
523 $fake_config = HTMLPurifier_Config::createDefault();
524 $fake_def = $fake_config->getHTMLDefinition();
525 list($mock, $config) = $this->setupCacheMock('HTML');
526 // should never frob cache
527 $mock->expectNever('add');
528 $mock->expectNever('set');
529 $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->testDefinitionCachingOptimizedHit');
530 $mock->expectOnce('get');
531 $mock->setReturnValue('get', $fake_def);
532 $this->assertNull($config->maybeGetRawHTMLDefinition());
533 $config->getDefinition('HTML');
534 $config->getDefinition('HTML');
535 $this->teardownCacheMock();
538 protected function setupCacheMock($type) {
539 // inject our definition cache mock globally (borrowed from
540 // DefinitionFactoryTest)
541 generate_mock_once("HTMLPurifier_DefinitionCacheFactory");
542 $factory = new HTMLPurifier_DefinitionCacheFactoryMock();
543 $this->oldFactory = HTMLPurifier_DefinitionCacheFactory::instance();
544 HTMLPurifier_DefinitionCacheFactory::instance($factory);
545 generate_mock_once("HTMLPurifier_DefinitionCache");
546 $mock = new HTMLPurifier_DefinitionCacheMock();
547 $config = HTMLPurifier_Config::createDefault();
548 $factory->setReturnValue('create', $mock, array($type, $config));
549 return array($mock, $config);
551 protected function teardownCacheMock() {
552 HTMLPurifier_DefinitionCacheFactory::instance($this->oldFactory);
557 // vim: et sw=4 sts=4