See Release Notes
Long Term Support Release
1 <?php 2 3 /** 4 * Definition of the purified HTML that describes allowed children, 5 * attributes, and many other things. 6 * 7 * Conventions: 8 * 9 * All member variables that are prefixed with info 10 * (including the main $info array) are used by HTML Purifier internals 11 * and should not be directly edited when customizing the HTMLDefinition. 12 * They can usually be set via configuration directives or custom 13 * modules. 14 * 15 * On the other hand, member variables without the info prefix are used 16 * internally by the HTMLDefinition and MUST NOT be used by other HTML 17 * Purifier internals. Many of them, however, are public, and may be 18 * edited by userspace code to tweak the behavior of HTMLDefinition. 19 * 20 * @note This class is inspected by Printer_HTMLDefinition; please 21 * update that class if things here change. 22 * 23 * @warning Directives that change this object's structure must be in 24 * the HTML or Attr namespace! 25 */ 26 class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition 27 { 28 29 // FULLY-PUBLIC VARIABLES --------------------------------------------- 30 31 /** 32 * Associative array of element names to HTMLPurifier_ElementDef. 33 * @type HTMLPurifier_ElementDef[] 34 */ 35 public $info = array(); 36 37 /** 38 * Associative array of global attribute name to attribute definition. 39 * @type array 40 */ 41 public $info_global_attr = array(); 42 43 /** 44 * String name of parent element HTML will be going into. 45 * @type string 46 */ 47 public $info_parent = 'div'; 48 49 /** 50 * Definition for parent element, allows parent element to be a 51 * tag that's not allowed inside the HTML fragment. 52 * @type HTMLPurifier_ElementDef 53 */ 54 public $info_parent_def; 55 56 /** 57 * String name of element used to wrap inline elements in block context. 58 * @type string 59 * @note This is rarely used except for BLOCKQUOTEs in strict mode 60 */ 61 public $info_block_wrapper = 'p'; 62 63 /** 64 * Associative array of deprecated tag name to HTMLPurifier_TagTransform. 65 * @type array 66 */ 67 public $info_tag_transform = array(); 68 69 /** 70 * Indexed list of HTMLPurifier_AttrTransform to be performed before validation. 71 * @type HTMLPurifier_AttrTransform[] 72 */ 73 public $info_attr_transform_pre = array(); 74 75 /** 76 * Indexed list of HTMLPurifier_AttrTransform to be performed after validation. 77 * @type HTMLPurifier_AttrTransform[] 78 */ 79 public $info_attr_transform_post = array(); 80 81 /** 82 * Nested lookup array of content set name (Block, Inline) to 83 * element name to whether or not it belongs in that content set. 84 * @type array 85 */ 86 public $info_content_sets = array(); 87 88 /** 89 * Indexed list of HTMLPurifier_Injector to be used. 90 * @type HTMLPurifier_Injector[] 91 */ 92 public $info_injector = array(); 93 94 /** 95 * Doctype object 96 * @type HTMLPurifier_Doctype 97 */ 98 public $doctype; 99 100 101 102 // RAW CUSTOMIZATION STUFF -------------------------------------------- 103 104 /** 105 * Adds a custom attribute to a pre-existing element 106 * @note This is strictly convenience, and does not have a corresponding 107 * method in HTMLPurifier_HTMLModule 108 * @param string $element_name Element name to add attribute to 109 * @param string $attr_name Name of attribute 110 * @param mixed $def Attribute definition, can be string or object, see 111 * HTMLPurifier_AttrTypes for details 112 */ 113 public function addAttribute($element_name, $attr_name, $def) 114 { 115 $module = $this->getAnonymousModule(); 116 if (!isset($module->info[$element_name])) { 117 $element = $module->addBlankElement($element_name); 118 } else { 119 $element = $module->info[$element_name]; 120 } 121 $element->attr[$attr_name] = $def; 122 } 123 124 /** 125 * Adds a custom element to your HTML definition 126 * @see HTMLPurifier_HTMLModule::addElement() for detailed 127 * parameter and return value descriptions. 128 */ 129 public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) 130 { 131 $module = $this->getAnonymousModule(); 132 // assume that if the user is calling this, the element 133 // is safe. This may not be a good idea 134 $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes); 135 return $element; 136 } 137 138 /** 139 * Adds a blank element to your HTML definition, for overriding 140 * existing behavior 141 * @param string $element_name 142 * @return HTMLPurifier_ElementDef 143 * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed 144 * parameter and return value descriptions. 145 */ 146 public function addBlankElement($element_name) 147 { 148 $module = $this->getAnonymousModule(); 149 $element = $module->addBlankElement($element_name); 150 return $element; 151 } 152 153 /** 154 * Retrieves a reference to the anonymous module, so you can 155 * bust out advanced features without having to make your own 156 * module. 157 * @return HTMLPurifier_HTMLModule 158 */ 159 public function getAnonymousModule() 160 { 161 if (!$this->_anonModule) { 162 $this->_anonModule = new HTMLPurifier_HTMLModule(); 163 $this->_anonModule->name = 'Anonymous'; 164 } 165 return $this->_anonModule; 166 } 167 168 private $_anonModule = null; 169 170 // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- 171 172 /** 173 * @type string 174 */ 175 public $type = 'HTML'; 176 177 /** 178 * @type HTMLPurifier_HTMLModuleManager 179 */ 180 public $manager; 181 182 /** 183 * Performs low-cost, preliminary initialization. 184 */ 185 public function __construct() 186 { 187 $this->manager = new HTMLPurifier_HTMLModuleManager(); 188 } 189 190 /** 191 * @param HTMLPurifier_Config $config 192 */ 193 protected function doSetup($config) 194 { 195 $this->processModules($config); 196 $this->setupConfigStuff($config); 197 unset($this->manager); 198 199 // cleanup some of the element definitions 200 foreach ($this->info as $k => $v) { 201 unset($this->info[$k]->content_model); 202 unset($this->info[$k]->content_model_type); 203 } 204 } 205 206 /** 207 * Extract out the information from the manager 208 * @param HTMLPurifier_Config $config 209 */ 210 protected function processModules($config) 211 { 212 if ($this->_anonModule) { 213 // for user specific changes 214 // this is late-loaded so we don't have to deal with PHP4 215 // reference wonky-ness 216 $this->manager->addModule($this->_anonModule); 217 unset($this->_anonModule); 218 } 219 220 $this->manager->setup($config); 221 $this->doctype = $this->manager->doctype; 222 223 foreach ($this->manager->modules as $module) { 224 foreach ($module->info_tag_transform as $k => $v) { 225 if ($v === false) { 226 unset($this->info_tag_transform[$k]); 227 } else { 228 $this->info_tag_transform[$k] = $v; 229 } 230 } 231 foreach ($module->info_attr_transform_pre as $k => $v) { 232 if ($v === false) { 233 unset($this->info_attr_transform_pre[$k]); 234 } else { 235 $this->info_attr_transform_pre[$k] = $v; 236 } 237 } 238 foreach ($module->info_attr_transform_post as $k => $v) { 239 if ($v === false) { 240 unset($this->info_attr_transform_post[$k]); 241 } else { 242 $this->info_attr_transform_post[$k] = $v; 243 } 244 } 245 foreach ($module->info_injector as $k => $v) { 246 if ($v === false) { 247 unset($this->info_injector[$k]); 248 } else { 249 $this->info_injector[$k] = $v; 250 } 251 } 252 } 253 $this->info = $this->manager->getElements(); 254 $this->info_content_sets = $this->manager->contentSets->lookup; 255 } 256 257 /** 258 * Sets up stuff based on config. We need a better way of doing this. 259 * @param HTMLPurifier_Config $config 260 */ 261 protected function setupConfigStuff($config) 262 { 263 $block_wrapper = $config->get('HTML.BlockWrapper'); 264 if (isset($this->info_content_sets['Block'][$block_wrapper])) { 265 $this->info_block_wrapper = $block_wrapper; 266 } else { 267 trigger_error( 268 'Cannot use non-block element as block wrapper', 269 E_USER_ERROR 270 ); 271 } 272 273 $parent = $config->get('HTML.Parent'); 274 $def = $this->manager->getElement($parent, true); 275 if ($def) { 276 $this->info_parent = $parent; 277 $this->info_parent_def = $def; 278 } else { 279 trigger_error( 280 'Cannot use unrecognized element as parent', 281 E_USER_ERROR 282 ); 283 $this->info_parent_def = $this->manager->getElement($this->info_parent, true); 284 } 285 286 // support template text 287 $support = "(for information on implementing this, see the support forums) "; 288 289 // setup allowed elements ----------------------------------------- 290 291 $allowed_elements = $config->get('HTML.AllowedElements'); 292 $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early 293 294 if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { 295 $allowed = $config->get('HTML.Allowed'); 296 if (is_string($allowed)) { 297 list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed); 298 } 299 } 300 301 if (is_array($allowed_elements)) { 302 foreach ($this->info as $name => $d) { 303 if (!isset($allowed_elements[$name])) { 304 unset($this->info[$name]); 305 } 306 unset($allowed_elements[$name]); 307 } 308 // emit errors 309 foreach ($allowed_elements as $element => $d) { 310 $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! 311 trigger_error("Element '$element' is not supported $support", E_USER_WARNING); 312 } 313 } 314 315 // setup allowed attributes --------------------------------------- 316 317 $allowed_attributes_mutable = $allowed_attributes; // by copy! 318 if (is_array($allowed_attributes)) { 319 // This actually doesn't do anything, since we went away from 320 // global attributes. It's possible that userland code uses 321 // it, but HTMLModuleManager doesn't! 322 foreach ($this->info_global_attr as $attr => $x) { 323 $keys = array($attr, "*@$attr", "*.$attr"); 324 $delete = true; 325 foreach ($keys as $key) { 326 if ($delete && isset($allowed_attributes[$key])) { 327 $delete = false; 328 } 329 if (isset($allowed_attributes_mutable[$key])) { 330 unset($allowed_attributes_mutable[$key]); 331 } 332 } 333 if ($delete) { 334 unset($this->info_global_attr[$attr]); 335 } 336 } 337 338 foreach ($this->info as $tag => $info) { 339 foreach ($info->attr as $attr => $x) { 340 $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); 341 $delete = true; 342 foreach ($keys as $key) { 343 if ($delete && isset($allowed_attributes[$key])) { 344 $delete = false; 345 } 346 if (isset($allowed_attributes_mutable[$key])) { 347 unset($allowed_attributes_mutable[$key]); 348 } 349 } 350 if ($delete) { 351 if ($this->info[$tag]->attr[$attr]->required) { 352 trigger_error( 353 "Required attribute '$attr' in element '$tag' " . 354 "was not allowed, which means '$tag' will not be allowed either", 355 E_USER_WARNING 356 ); 357 } 358 unset($this->info[$tag]->attr[$attr]); 359 } 360 } 361 } 362 // emit errors 363 foreach ($allowed_attributes_mutable as $elattr => $d) { 364 $bits = preg_split('/[.@]/', $elattr, 2); 365 $c = count($bits); 366 switch ($c) { 367 case 2: 368 if ($bits[0] !== '*') { 369 $element = htmlspecialchars($bits[0]); 370 $attribute = htmlspecialchars($bits[1]); 371 if (!isset($this->info[$element])) { 372 trigger_error( 373 "Cannot allow attribute '$attribute' if element " . 374 "'$element' is not allowed/supported $support" 375 ); 376 } else { 377 trigger_error( 378 "Attribute '$attribute' in element '$element' not supported $support", 379 E_USER_WARNING 380 ); 381 } 382 break; 383 } 384 // otherwise fall through 385 case 1: 386 $attribute = htmlspecialchars($bits[0]); 387 trigger_error( 388 "Global attribute '$attribute' is not ". 389 "supported in any elements $support", 390 E_USER_WARNING 391 ); 392 break; 393 } 394 } 395 } 396 397 // setup forbidden elements --------------------------------------- 398 399 $forbidden_elements = $config->get('HTML.ForbiddenElements'); 400 $forbidden_attributes = $config->get('HTML.ForbiddenAttributes'); 401 402 foreach ($this->info as $tag => $info) { 403 if (isset($forbidden_elements[$tag])) { 404 unset($this->info[$tag]); 405 continue; 406 } 407 foreach ($info->attr as $attr => $x) { 408 if (isset($forbidden_attributes["$tag@$attr"]) || 409 isset($forbidden_attributes["*@$attr"]) || 410 isset($forbidden_attributes[$attr]) 411 ) { 412 unset($this->info[$tag]->attr[$attr]); 413 continue; 414 } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually 415 // $tag.$attr are not user supplied, so no worries! 416 trigger_error( 417 "Error with $tag.$attr: tag.attr syntax not supported for " . 418 "HTML.ForbiddenAttributes; use tag@attr instead", 419 E_USER_WARNING 420 ); 421 } 422 } 423 } 424 foreach ($forbidden_attributes as $key => $v) { 425 if (strlen($key) < 2) { 426 continue; 427 } 428 if ($key[0] != '*') { 429 continue; 430 } 431 if ($key[1] == '.') { 432 trigger_error( 433 "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", 434 E_USER_WARNING 435 ); 436 } 437 } 438 439 // setup injectors ----------------------------------------------------- 440 foreach ($this->info_injector as $i => $injector) { 441 if ($injector->checkNeeded($config) !== false) { 442 // remove injector that does not have it's required 443 // elements/attributes present, and is thus not needed. 444 unset($this->info_injector[$i]); 445 } 446 } 447 } 448 449 /** 450 * Parses a TinyMCE-flavored Allowed Elements and Attributes list into 451 * separate lists for processing. Format is element[attr1|attr2],element2... 452 * @warning Although it's largely drawn from TinyMCE's implementation, 453 * it is different, and you'll probably have to modify your lists 454 * @param array $list String list to parse 455 * @return array 456 * @todo Give this its own class, probably static interface 457 */ 458 public function parseTinyMCEAllowedList($list) 459 { 460 $list = str_replace(array(' ', "\t"), '', $list); 461 462 $elements = array(); 463 $attributes = array(); 464 465 $chunks = preg_split('/(,|[\n\r]+)/', $list); 466 foreach ($chunks as $chunk) { 467 if (empty($chunk)) { 468 continue; 469 } 470 // remove TinyMCE element control characters 471 if (!strpos($chunk, '[')) { 472 $element = $chunk; 473 $attr = false; 474 } else { 475 list($element, $attr) = explode('[', $chunk); 476 } 477 if ($element !== '*') { 478 $elements[$element] = true; 479 } 480 if (!$attr) { 481 continue; 482 } 483 $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ] 484 $attr = explode('|', $attr); 485 foreach ($attr as $key) { 486 $attributes["$element.$key"] = true; 487 } 488 } 489 return array($elements, $attributes); 490 } 491 } 492 493 // vim: et sw=4 sts=4
title
Description
Body
title
Description
Body
title
Description
Body
title
Body