1<?php 2 3/** 4 * Defines allowed CSS attributes and what their values are. 5 * @see HTMLPurifier_HTMLDefinition 6 */ 7class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition 8{ 9 10 public $type = 'CSS'; 11 12 /** 13 * Assoc array of attribute name to definition object. 14 * @type HTMLPurifier_AttrDef[] 15 */ 16 public $info = array(); 17 18 /** 19 * Constructs the info array. The meat of this class. 20 * @param HTMLPurifier_Config $config 21 */ 22 protected function doSetup($config) 23 { 24 $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( 25 array('left', 'right', 'center', 'justify'), 26 false 27 ); 28 29 $border_style = 30 $this->info['border-bottom-style'] = 31 $this->info['border-right-style'] = 32 $this->info['border-left-style'] = 33 $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( 34 array( 35 'none', 36 'hidden', 37 'dotted', 38 'dashed', 39 'solid', 40 'double', 41 'groove', 42 'ridge', 43 'inset', 44 'outset' 45 ), 46 false 47 ); 48 49 $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); 50 51 $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( 52 array('none', 'left', 'right', 'both'), 53 false 54 ); 55 $this->info['float'] = new HTMLPurifier_AttrDef_Enum( 56 array('none', 'left', 'right'), 57 false 58 ); 59 $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( 60 array('normal', 'italic', 'oblique'), 61 false 62 ); 63 $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( 64 array('normal', 'small-caps'), 65 false 66 ); 67 68 $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( 69 array( 70 new HTMLPurifier_AttrDef_Enum(array('none')), 71 new HTMLPurifier_AttrDef_CSS_URI() 72 ) 73 ); 74 75 $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( 76 array('inside', 'outside'), 77 false 78 ); 79 $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( 80 array( 81 'disc', 82 'circle', 83 'square', 84 'decimal', 85 'lower-roman', 86 'upper-roman', 87 'lower-alpha', 88 'upper-alpha', 89 'none' 90 ), 91 false 92 ); 93 $this->info['list-style-image'] = $uri_or_none; 94 95 $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); 96 97 $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( 98 array('capitalize', 'uppercase', 'lowercase', 'none'), 99 false 100 ); 101 $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); 102 103 $this->info['background-image'] = $uri_or_none; 104 $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( 105 array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') 106 ); 107 $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( 108 array('scroll', 'fixed') 109 ); 110 $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); 111 112 $border_color = 113 $this->info['border-top-color'] = 114 $this->info['border-bottom-color'] = 115 $this->info['border-left-color'] = 116 $this->info['border-right-color'] = 117 $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite( 118 array( 119 new HTMLPurifier_AttrDef_Enum(array('transparent')), 120 new HTMLPurifier_AttrDef_CSS_Color() 121 ) 122 ); 123 124 $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); 125 126 $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); 127 128 $border_width = 129 $this->info['border-top-width'] = 130 $this->info['border-bottom-width'] = 131 $this->info['border-left-width'] = 132 $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite( 133 array( 134 new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), 135 new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative 136 ) 137 ); 138 139 $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); 140 141 $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( 142 array( 143 new HTMLPurifier_AttrDef_Enum(array('normal')), 144 new HTMLPurifier_AttrDef_CSS_Length() 145 ) 146 ); 147 148 $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( 149 array( 150 new HTMLPurifier_AttrDef_Enum(array('normal')), 151 new HTMLPurifier_AttrDef_CSS_Length() 152 ) 153 ); 154 155 $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( 156 array( 157 new HTMLPurifier_AttrDef_Enum( 158 array( 159 'xx-small', 160 'x-small', 161 'small', 162 'medium', 163 'large', 164 'x-large', 165 'xx-large', 166 'larger', 167 'smaller' 168 ) 169 ), 170 new HTMLPurifier_AttrDef_CSS_Percentage(), 171 new HTMLPurifier_AttrDef_CSS_Length() 172 ) 173 ); 174 175 $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite( 176 array( 177 new HTMLPurifier_AttrDef_Enum(array('normal')), 178 new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives 179 new HTMLPurifier_AttrDef_CSS_Length('0'), 180 new HTMLPurifier_AttrDef_CSS_Percentage(true) 181 ) 182 ); 183 184 $margin = 185 $this->info['margin-top'] = 186 $this->info['margin-bottom'] = 187 $this->info['margin-left'] = 188 $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite( 189 array( 190 new HTMLPurifier_AttrDef_CSS_Length(), 191 new HTMLPurifier_AttrDef_CSS_Percentage(), 192 new HTMLPurifier_AttrDef_Enum(array('auto')) 193 ) 194 ); 195 196 $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); 197 198 // non-negative 199 $padding = 200 $this->info['padding-top'] = 201 $this->info['padding-bottom'] = 202 $this->info['padding-left'] = 203 $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite( 204 array( 205 new HTMLPurifier_AttrDef_CSS_Length('0'), 206 new HTMLPurifier_AttrDef_CSS_Percentage(true) 207 ) 208 ); 209 210 $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); 211 212 $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite( 213 array( 214 new HTMLPurifier_AttrDef_CSS_Length(), 215 new HTMLPurifier_AttrDef_CSS_Percentage() 216 ) 217 ); 218 219 $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( 220 array( 221 new HTMLPurifier_AttrDef_CSS_Length('0'), 222 new HTMLPurifier_AttrDef_CSS_Percentage(true), 223 new HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit')) 224 ) 225 ); 226 $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite( 227 array( 228 new HTMLPurifier_AttrDef_CSS_Length('0'), 229 new HTMLPurifier_AttrDef_CSS_Percentage(true), 230 new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')) 231 ) 232 ); 233 $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite( 234 array( 235 new HTMLPurifier_AttrDef_CSS_Length('0'), 236 new HTMLPurifier_AttrDef_CSS_Percentage(true), 237 new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')) 238 ) 239 ); 240 $max = $config->get('CSS.MaxImgLength'); 241 242 $this->info['width'] = 243 $this->info['height'] = 244 $max === null ? 245 $trusted_wh : 246 new HTMLPurifier_AttrDef_Switch( 247 'img', 248 // For img tags: 249 new HTMLPurifier_AttrDef_CSS_Composite( 250 array( 251 new HTMLPurifier_AttrDef_CSS_Length('0', $max), 252 new HTMLPurifier_AttrDef_Enum(array('auto')) 253 ) 254 ), 255 // For everyone else: 256 $trusted_wh 257 ); 258 $this->info['min-width'] = 259 $this->info['min-height'] = 260 $max === null ? 261 $trusted_min_wh : 262 new HTMLPurifier_AttrDef_Switch( 263 'img', 264 // For img tags: 265 new HTMLPurifier_AttrDef_CSS_Composite( 266 array( 267 new HTMLPurifier_AttrDef_CSS_Length('0', $max), 268 new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')) 269 ) 270 ), 271 // For everyone else: 272 $trusted_min_wh 273 ); 274 $this->info['max-width'] = 275 $this->info['max-height'] = 276 $max === null ? 277 $trusted_max_wh : 278 new HTMLPurifier_AttrDef_Switch( 279 'img', 280 // For img tags: 281 new HTMLPurifier_AttrDef_CSS_Composite( 282 array( 283 new HTMLPurifier_AttrDef_CSS_Length('0', $max), 284 new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')) 285 ) 286 ), 287 // For everyone else: 288 $trusted_max_wh 289 ); 290 291 $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); 292 293 $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); 294 295 // this could use specialized code 296 $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( 297 array( 298 'normal', 299 'bold', 300 'bolder', 301 'lighter', 302 '100', 303 '200', 304 '300', 305 '400', 306 '500', 307 '600', 308 '700', 309 '800', 310 '900' 311 ), 312 false 313 ); 314 315 // MUST be called after other font properties, as it references 316 // a CSSDefinition object 317 $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); 318 319 // same here 320 $this->info['border'] = 321 $this->info['border-bottom'] = 322 $this->info['border-top'] = 323 $this->info['border-left'] = 324 $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); 325 326 $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( 327 array('collapse', 'separate') 328 ); 329 330 $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( 331 array('top', 'bottom') 332 ); 333 334 $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( 335 array('auto', 'fixed') 336 ); 337 338 $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( 339 array( 340 new HTMLPurifier_AttrDef_Enum( 341 array( 342 'baseline', 343 'sub', 344 'super', 345 'top', 346 'text-top', 347 'middle', 348 'bottom', 349 'text-bottom' 350 ) 351 ), 352 new HTMLPurifier_AttrDef_CSS_Length(), 353 new HTMLPurifier_AttrDef_CSS_Percentage() 354 ) 355 ); 356 357 $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); 358 359 // These CSS properties don't work on many browsers, but we live 360 // in THE FUTURE! 361 $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( 362 array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') 363 ); 364 365 if ($config->get('CSS.Proprietary')) { 366 $this->doSetupProprietary($config); 367 } 368 369 if ($config->get('CSS.AllowTricky')) { 370 $this->doSetupTricky($config); 371 } 372 373 if ($config->get('CSS.Trusted')) { 374 $this->doSetupTrusted($config); 375 } 376 377 $allow_important = $config->get('CSS.AllowImportant'); 378 // wrap all attr-defs with decorator that handles !important 379 foreach ($this->info as $k => $v) { 380 $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); 381 } 382 383 $this->setupConfigStuff($config); 384 } 385 386 /** 387 * @param HTMLPurifier_Config $config 388 */ 389 protected function doSetupProprietary($config) 390 { 391 // Internet Explorer only scrollbar colors 392 $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 393 $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 394 $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 395 $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 396 $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 397 $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 398 399 // vendor specific prefixes of opacity 400 $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); 401 $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); 402 403 // only opacity, for now 404 $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); 405 406 // more CSS3 407 $this->info['page-break-after'] = 408 $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum( 409 array( 410 'auto', 411 'always', 412 'avoid', 413 'left', 414 'right' 415 ) 416 ); 417 $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); 418 419 $border_radius = new HTMLPurifier_AttrDef_CSS_Composite( 420 array( 421 new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative 422 new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative 423 )); 424 425 $this->info['border-top-left-radius'] = 426 $this->info['border-top-right-radius'] = 427 $this->info['border-bottom-right-radius'] = 428 $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2); 429 // TODO: support SLASH syntax 430 $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4); 431 432 } 433 434 /** 435 * @param HTMLPurifier_Config $config 436 */ 437 protected function doSetupTricky($config) 438 { 439 $this->info['display'] = new HTMLPurifier_AttrDef_Enum( 440 array( 441 'inline', 442 'block', 443 'list-item', 444 'run-in', 445 'compact', 446 'marker', 447 'table', 448 'inline-block', 449 'inline-table', 450 'table-row-group', 451 'table-header-group', 452 'table-footer-group', 453 'table-row', 454 'table-column-group', 455 'table-column', 456 'table-cell', 457 'table-caption', 458 'none' 459 ) 460 ); 461 $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum( 462 array('visible', 'hidden', 'collapse') 463 ); 464 $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); 465 $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); 466 } 467 468 /** 469 * @param HTMLPurifier_Config $config 470 */ 471 protected function doSetupTrusted($config) 472 { 473 $this->info['position'] = new HTMLPurifier_AttrDef_Enum( 474 array('static', 'relative', 'absolute', 'fixed') 475 ); 476 $this->info['top'] = 477 $this->info['left'] = 478 $this->info['right'] = 479 $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( 480 array( 481 new HTMLPurifier_AttrDef_CSS_Length(), 482 new HTMLPurifier_AttrDef_CSS_Percentage(), 483 new HTMLPurifier_AttrDef_Enum(array('auto')), 484 ) 485 ); 486 $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( 487 array( 488 new HTMLPurifier_AttrDef_Integer(), 489 new HTMLPurifier_AttrDef_Enum(array('auto')), 490 ) 491 ); 492 } 493 494 /** 495 * Performs extra config-based processing. Based off of 496 * HTMLPurifier_HTMLDefinition. 497 * @param HTMLPurifier_Config $config 498 * @todo Refactor duplicate elements into common class (probably using 499 * composition, not inheritance). 500 */ 501 protected function setupConfigStuff($config) 502 { 503 // setup allowed elements 504 $support = "(for information on implementing this, see the " . 505 "support forums) "; 506 $allowed_properties = $config->get('CSS.AllowedProperties'); 507 if ($allowed_properties !== null) { 508 foreach ($this->info as $name => $d) { 509 if (!isset($allowed_properties[$name])) { 510 unset($this->info[$name]); 511 } 512 unset($allowed_properties[$name]); 513 } 514 // emit errors 515 foreach ($allowed_properties as $name => $d) { 516 // :TODO: Is this htmlspecialchars() call really necessary? 517 $name = htmlspecialchars($name); 518 trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); 519 } 520 } 521 522 $forbidden_properties = $config->get('CSS.ForbiddenProperties'); 523 if ($forbidden_properties !== null) { 524 foreach ($this->info as $name => $d) { 525 if (isset($forbidden_properties[$name])) { 526 unset($this->info[$name]); 527 } 528 } 529 } 530 } 531} 532 533// vim: et sw=4 sts=4 534