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')) 224 ) 225 ); 226 $max = $config->get('CSS.MaxImgLength'); 227 228 $this->info['min-width'] = 229 $this->info['max-width'] = 230 $this->info['min-height'] = 231 $this->info['max-height'] = 232 $this->info['width'] = 233 $this->info['height'] = 234 $max === null ? 235 $trusted_wh : 236 new HTMLPurifier_AttrDef_Switch( 237 'img', 238 // For img tags: 239 new HTMLPurifier_AttrDef_CSS_Composite( 240 array( 241 new HTMLPurifier_AttrDef_CSS_Length('0', $max), 242 new HTMLPurifier_AttrDef_Enum(array('auto')) 243 ) 244 ), 245 // For everyone else: 246 $trusted_wh 247 ); 248 249 $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); 250 251 $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); 252 253 // this could use specialized code 254 $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( 255 array( 256 'normal', 257 'bold', 258 'bolder', 259 'lighter', 260 '100', 261 '200', 262 '300', 263 '400', 264 '500', 265 '600', 266 '700', 267 '800', 268 '900' 269 ), 270 false 271 ); 272 273 // MUST be called after other font properties, as it references 274 // a CSSDefinition object 275 $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); 276 277 // same here 278 $this->info['border'] = 279 $this->info['border-bottom'] = 280 $this->info['border-top'] = 281 $this->info['border-left'] = 282 $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); 283 284 $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( 285 array('collapse', 'separate') 286 ); 287 288 $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( 289 array('top', 'bottom') 290 ); 291 292 $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( 293 array('auto', 'fixed') 294 ); 295 296 $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( 297 array( 298 new HTMLPurifier_AttrDef_Enum( 299 array( 300 'baseline', 301 'sub', 302 'super', 303 'top', 304 'text-top', 305 'middle', 306 'bottom', 307 'text-bottom' 308 ) 309 ), 310 new HTMLPurifier_AttrDef_CSS_Length(), 311 new HTMLPurifier_AttrDef_CSS_Percentage() 312 ) 313 ); 314 315 $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); 316 317 // These CSS properties don't work on many browsers, but we live 318 // in THE FUTURE! 319 $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( 320 array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') 321 ); 322 323 if ($config->get('CSS.Proprietary')) { 324 $this->doSetupProprietary($config); 325 } 326 327 if ($config->get('CSS.AllowTricky')) { 328 $this->doSetupTricky($config); 329 } 330 331 if ($config->get('CSS.Trusted')) { 332 $this->doSetupTrusted($config); 333 } 334 335 $allow_important = $config->get('CSS.AllowImportant'); 336 // wrap all attr-defs with decorator that handles !important 337 foreach ($this->info as $k => $v) { 338 $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); 339 } 340 341 $this->setupConfigStuff($config); 342 } 343 344 /** 345 * @param HTMLPurifier_Config $config 346 */ 347 protected function doSetupProprietary($config) 348 { 349 // Internet Explorer only scrollbar colors 350 $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 351 $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 352 $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 353 $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 354 $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 355 $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); 356 357 // vendor specific prefixes of opacity 358 $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); 359 $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); 360 361 // only opacity, for now 362 $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); 363 364 // more CSS3 365 $this->info['page-break-after'] = 366 $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum( 367 array( 368 'auto', 369 'always', 370 'avoid', 371 'left', 372 'right' 373 ) 374 ); 375 $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); 376 377 $border_radius = new HTMLPurifier_AttrDef_CSS_Composite( 378 array( 379 new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative 380 new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative 381 )); 382 383 $this->info['border-top-left-radius'] = 384 $this->info['border-top-right-radius'] = 385 $this->info['border-bottom-right-radius'] = 386 $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2); 387 // TODO: support SLASH syntax 388 $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4); 389 390 } 391 392 /** 393 * @param HTMLPurifier_Config $config 394 */ 395 protected function doSetupTricky($config) 396 { 397 $this->info['display'] = new HTMLPurifier_AttrDef_Enum( 398 array( 399 'inline', 400 'block', 401 'list-item', 402 'run-in', 403 'compact', 404 'marker', 405 'table', 406 'inline-block', 407 'inline-table', 408 'table-row-group', 409 'table-header-group', 410 'table-footer-group', 411 'table-row', 412 'table-column-group', 413 'table-column', 414 'table-cell', 415 'table-caption', 416 'none' 417 ) 418 ); 419 $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum( 420 array('visible', 'hidden', 'collapse') 421 ); 422 $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); 423 $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); 424 } 425 426 /** 427 * @param HTMLPurifier_Config $config 428 */ 429 protected function doSetupTrusted($config) 430 { 431 $this->info['position'] = new HTMLPurifier_AttrDef_Enum( 432 array('static', 'relative', 'absolute', 'fixed') 433 ); 434 $this->info['top'] = 435 $this->info['left'] = 436 $this->info['right'] = 437 $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( 438 array( 439 new HTMLPurifier_AttrDef_CSS_Length(), 440 new HTMLPurifier_AttrDef_CSS_Percentage(), 441 new HTMLPurifier_AttrDef_Enum(array('auto')), 442 ) 443 ); 444 $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( 445 array( 446 new HTMLPurifier_AttrDef_Integer(), 447 new HTMLPurifier_AttrDef_Enum(array('auto')), 448 ) 449 ); 450 } 451 452 /** 453 * Performs extra config-based processing. Based off of 454 * HTMLPurifier_HTMLDefinition. 455 * @param HTMLPurifier_Config $config 456 * @todo Refactor duplicate elements into common class (probably using 457 * composition, not inheritance). 458 */ 459 protected function setupConfigStuff($config) 460 { 461 // setup allowed elements 462 $support = "(for information on implementing this, see the " . 463 "support forums) "; 464 $allowed_properties = $config->get('CSS.AllowedProperties'); 465 if ($allowed_properties !== null) { 466 foreach ($this->info as $name => $d) { 467 if (!isset($allowed_properties[$name])) { 468 unset($this->info[$name]); 469 } 470 unset($allowed_properties[$name]); 471 } 472 // emit errors 473 foreach ($allowed_properties as $name => $d) { 474 // :TODO: Is this htmlspecialchars() call really necessary? 475 $name = htmlspecialchars($name); 476 trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); 477 } 478 } 479 480 $forbidden_properties = $config->get('CSS.ForbiddenProperties'); 481 if ($forbidden_properties !== null) { 482 foreach ($this->info as $name => $d) { 483 if (isset($forbidden_properties[$name])) { 484 unset($this->info[$name]); 485 } 486 } 487 } 488 } 489} 490 491// vim: et sw=4 sts=4 492