| 1: | <?php | 
| 2: |  | 
| 3: |  | 
| 4: |  | 
| 5: |  | 
| 6: |  | 
| 7: | function htmlpurifier_filter_extractstyleblocks_muteerrorhandler() | 
| 8: | { | 
| 9: | } | 
| 10: |  | 
| 11: |  | 
| 12: |  | 
| 13: |  | 
| 14: |  | 
| 15: |  | 
| 16: |  | 
| 17: |  | 
| 18: |  | 
| 19: |  | 
| 20: |  | 
| 21: |  | 
| 22: |  | 
| 23: |  | 
| 24: |  | 
| 25: | class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter | 
| 26: | { | 
| 27: |  | 
| 28: |  | 
| 29: |  | 
| 30: | public $name = 'ExtractStyleBlocks'; | 
| 31: |  | 
| 32: |  | 
| 33: |  | 
| 34: |  | 
| 35: | private $_styleMatches = array(); | 
| 36: |  | 
| 37: |  | 
| 38: |  | 
| 39: |  | 
| 40: | private $_tidy; | 
| 41: |  | 
| 42: |  | 
| 43: |  | 
| 44: |  | 
| 45: | private $_id_attrdef; | 
| 46: |  | 
| 47: |  | 
| 48: |  | 
| 49: |  | 
| 50: | private $_class_attrdef; | 
| 51: |  | 
| 52: |  | 
| 53: |  | 
| 54: |  | 
| 55: | private $_enum_attrdef; | 
| 56: |  | 
| 57: | public function __construct() | 
| 58: | { | 
| 59: | $this->_tidy = new csstidy(); | 
| 60: | $this->_tidy->set_cfg('lowercase_s', false); | 
| 61: | $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); | 
| 62: | $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); | 
| 63: | $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum( | 
| 64: | array( | 
| 65: | 'first-child', | 
| 66: | 'link', | 
| 67: | 'visited', | 
| 68: | 'active', | 
| 69: | 'hover', | 
| 70: | 'focus' | 
| 71: | ) | 
| 72: | ); | 
| 73: | } | 
| 74: |  | 
| 75: |  | 
| 76: |  | 
| 77: |  | 
| 78: |  | 
| 79: | protected function styleCallback($matches) | 
| 80: | { | 
| 81: | $this->_styleMatches[] = $matches[1]; | 
| 82: | } | 
| 83: |  | 
| 84: |  | 
| 85: |  | 
| 86: |  | 
| 87: |  | 
| 88: |  | 
| 89: |  | 
| 90: |  | 
| 91: |  | 
| 92: | public function preFilter($html, $config, $context) | 
| 93: | { | 
| 94: | $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl'); | 
| 95: | if ($tidy !== null) { | 
| 96: | $this->_tidy = $tidy; | 
| 97: | } | 
| 98: |  | 
| 99: |  | 
| 100: |  | 
| 101: | $html = preg_replace_callback('#<style(?:\s.*)?>(.*)<\/style>#isU', array($this, 'styleCallback'), $html); | 
| 102: | $style_blocks = $this->_styleMatches; | 
| 103: | $this->_styleMatches = array(); | 
| 104: | $context->register('StyleBlocks', $style_blocks); | 
| 105: | if ($this->_tidy) { | 
| 106: | foreach ($style_blocks as &$style) { | 
| 107: | $style = $this->cleanCSS($style, $config, $context); | 
| 108: | } | 
| 109: | } | 
| 110: | return $html; | 
| 111: | } | 
| 112: |  | 
| 113: |  | 
| 114: |  | 
| 115: |  | 
| 116: |  | 
| 117: |  | 
| 118: |  | 
| 119: |  | 
| 120: |  | 
| 121: |  | 
| 122: | public function cleanCSS($css, $config, $context) | 
| 123: | { | 
| 124: |  | 
| 125: | $scope = $config->get('Filter.ExtractStyleBlocks.Scope'); | 
| 126: | if ($scope !== null) { | 
| 127: | $scopes = array_map('trim', explode(',', $scope)); | 
| 128: | } else { | 
| 129: | $scopes = array(); | 
| 130: | } | 
| 131: |  | 
| 132: | $css = trim($css); | 
| 133: | if (strncmp('<!--', $css, 4) === 0) { | 
| 134: | $css = substr($css, 4); | 
| 135: | } | 
| 136: | if (strlen($css) > 3 && substr($css, -3) == '-->') { | 
| 137: | $css = substr($css, 0, -3); | 
| 138: | } | 
| 139: | $css = trim($css); | 
| 140: | set_error_handler('htmlpurifier_filter_extractstyleblocks_muteerrorhandler'); | 
| 141: | $this->_tidy->parse($css); | 
| 142: | restore_error_handler(); | 
| 143: | $css_definition = $config->getDefinition('CSS'); | 
| 144: | $html_definition = $config->getDefinition('HTML'); | 
| 145: | $new_css = array(); | 
| 146: | foreach ($this->_tidy->css as $k => $decls) { | 
| 147: |  | 
| 148: | $new_decls = array(); | 
| 149: | foreach ($decls as $selector => $style) { | 
| 150: | $selector = trim($selector); | 
| 151: | if ($selector === '') { | 
| 152: | continue; | 
| 153: | } | 
| 154: |  | 
| 155: |  | 
| 156: |  | 
| 157: |  | 
| 158: |  | 
| 159: |  | 
| 160: |  | 
| 161: |  | 
| 162: |  | 
| 163: |  | 
| 164: |  | 
| 165: |  | 
| 166: |  | 
| 167: |  | 
| 168: |  | 
| 169: |  | 
| 170: |  | 
| 171: |  | 
| 172: |  | 
| 173: |  | 
| 174: |  | 
| 175: |  | 
| 176: |  | 
| 177: |  | 
| 178: |  | 
| 179: |  | 
| 180: |  | 
| 181: |  | 
| 182: |  | 
| 183: |  | 
| 184: |  | 
| 185: |  | 
| 186: |  | 
| 187: |  | 
| 188: |  | 
| 189: |  | 
| 190: |  | 
| 191: |  | 
| 192: |  | 
| 193: |  | 
| 194: |  | 
| 195: |  | 
| 196: |  | 
| 197: |  | 
| 198: |  | 
| 199: |  | 
| 200: |  | 
| 201: |  | 
| 202: |  | 
| 203: |  | 
| 204: |  | 
| 205: |  | 
| 206: |  | 
| 207: |  | 
| 208: |  | 
| 209: |  | 
| 210: |  | 
| 211: |  | 
| 212: |  | 
| 213: |  | 
| 214: |  | 
| 215: |  | 
| 216: | $selectors = array_map('trim', explode(',', $selector)); | 
| 217: | $new_selectors = array(); | 
| 218: | foreach ($selectors as $sel) { | 
| 219: |  | 
| 220: | $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE); | 
| 221: |  | 
| 222: |  | 
| 223: | $nsel = null; | 
| 224: | $delim = null; | 
| 225: |  | 
| 226: | for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) { | 
| 227: | $x = $basic_selectors[$i]; | 
| 228: | if ($i % 2) { | 
| 229: |  | 
| 230: | if ($x === ' ') { | 
| 231: | $delim = ' '; | 
| 232: | } else { | 
| 233: | $delim = ' ' . $x . ' '; | 
| 234: | } | 
| 235: | } else { | 
| 236: |  | 
| 237: | $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE); | 
| 238: | $sdelim = null; | 
| 239: | $nx = null; | 
| 240: | for ($j = 0, $cc = count($components); $j < $cc; $j++) { | 
| 241: | $y = $components[$j]; | 
| 242: | if ($j === 0) { | 
| 243: | if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) { | 
| 244: | $nx = $y; | 
| 245: | } else { | 
| 246: |  | 
| 247: |  | 
| 248: |  | 
| 249: |  | 
| 250: |  | 
| 251: | } | 
| 252: | } elseif ($j % 2) { | 
| 253: |  | 
| 254: | $sdelim = $y; | 
| 255: | } else { | 
| 256: | $attrdef = null; | 
| 257: | if ($sdelim === '#') { | 
| 258: | $attrdef = $this->_id_attrdef; | 
| 259: | } elseif ($sdelim === '.') { | 
| 260: | $attrdef = $this->_class_attrdef; | 
| 261: | } elseif ($sdelim === ':') { | 
| 262: | $attrdef = $this->_enum_attrdef; | 
| 263: | } else { | 
| 264: | throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split'); | 
| 265: | } | 
| 266: | $r = $attrdef->validate($y, $config, $context); | 
| 267: | if ($r !== false) { | 
| 268: | if ($r !== true) { | 
| 269: | $y = $r; | 
| 270: | } | 
| 271: | if ($nx === null) { | 
| 272: | $nx = ''; | 
| 273: | } | 
| 274: | $nx .= $sdelim . $y; | 
| 275: | } | 
| 276: | } | 
| 277: | } | 
| 278: | if ($nx !== null) { | 
| 279: | if ($nsel === null) { | 
| 280: | $nsel = $nx; | 
| 281: | } else { | 
| 282: | $nsel .= $delim . $nx; | 
| 283: | } | 
| 284: | } else { | 
| 285: |  | 
| 286: |  | 
| 287: | } | 
| 288: | } | 
| 289: | } | 
| 290: | if ($nsel !== null) { | 
| 291: | if (!empty($scopes)) { | 
| 292: | foreach ($scopes as $s) { | 
| 293: | $new_selectors[] = "$s $nsel"; | 
| 294: | } | 
| 295: | } else { | 
| 296: | $new_selectors[] = $nsel; | 
| 297: | } | 
| 298: | } | 
| 299: | } | 
| 300: | if (empty($new_selectors)) { | 
| 301: | continue; | 
| 302: | } | 
| 303: | $selector = implode(', ', $new_selectors); | 
| 304: | foreach ($style as $name => $value) { | 
| 305: | if (!isset($css_definition->info[$name])) { | 
| 306: | unset($style[$name]); | 
| 307: | continue; | 
| 308: | } | 
| 309: | $def = $css_definition->info[$name]; | 
| 310: | $ret = $def->validate($value, $config, $context); | 
| 311: | if ($ret === false) { | 
| 312: | unset($style[$name]); | 
| 313: | } else { | 
| 314: | $style[$name] = $ret; | 
| 315: | } | 
| 316: | } | 
| 317: | $new_decls[$selector] = $style; | 
| 318: | } | 
| 319: | $new_css[$k] = $new_decls; | 
| 320: | } | 
| 321: |  | 
| 322: |  | 
| 323: | $this->_tidy->css = $new_css; | 
| 324: | $this->_tidy->import = array(); | 
| 325: | $this->_tidy->charset = null; | 
| 326: | $this->_tidy->namespace = null; | 
| 327: | $css = $this->_tidy->print->plain(); | 
| 328: |  | 
| 329: |  | 
| 330: | if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { | 
| 331: | $css = str_replace( | 
| 332: | array('<', '>', '&'), | 
| 333: | array('\3C ', '\3E ', '\26 '), | 
| 334: | $css | 
| 335: | ); | 
| 336: | } | 
| 337: | return $css; | 
| 338: | } | 
| 339: | } | 
| 340: |  | 
| 341: |  | 
| 342: |  |