/** * Replace all css definitions with #container [def] * and remove css-inlined scripting * * @param string CSS source code * @param string Container ID to use as prefix * * @return string Modified CSS source */ public static function mod_css_styles($source, $container_id, $allow_remote = false) { $last_pos = 0; $replacements = new rcube_string_replacer(); // ignore the whole block if evil styles are detected $source = self::xss_entity_decode($source); $stripped = preg_replace('/[^a-z\\(:;]/i', '', $source); $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\\(' : ''); if (preg_match("/{$evilexpr}/i", $stripped)) { return '/* evil! */'; } // cut out all contents between { and } while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { $styles = substr($source, $pos + 1, $pos2 - ($pos + 1)); // check every line of a style block... if ($allow_remote) { $a_styles = preg_split('/;[\\r\\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); foreach ($a_styles as $line) { $stripped = preg_replace('/[^a-z\\(:;]/i', '', $line); // ... and only allow strict url() values $regexp = '!url\\s*\\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\\)!Uims'; if (stripos($stripped, 'url(') && !preg_match($regexp, $line)) { $a_styles = array('/* evil! */'); break; } } $styles = join(";\n", $a_styles); } $key = $replacements->add($styles); $source = substr($source, 0, $pos + 1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source) - $pos2); $last_pos = $pos + 2; } // remove html comments and add #container to each tag selector. // also replace body definition because we also stripped off the <body> tag $styles = preg_replace(array('/(^\\s*<!--)|(-->\\s*$)/', '/(^\\s*|,\\s*|\\}\\s*)([a-z0-9\\._#\\*][a-z0-9\\.\\-_]*)/im', '/' . preg_quote($container_id, '/') . '\\s+body/i'), array('', "\\1#{$container_id} \\2", $container_id), $source); // put block contents back in $styles = $replacements->resolve($styles); return $styles; }
/** * Replace all css definitions with #container [def] * and remove css-inlined scripting * * @param string CSS source code * @param string Container ID to use as prefix * * @return string Modified CSS source */ public static function mod_css_styles($source, $container_id, $allow_remote=false) { $last_pos = 0; $replacements = new rcube_string_replacer; // ignore the whole block if evil styles are detected $source = self::xss_entity_decode($source); $stripped = preg_replace('/[^a-z\(:;]/i', '', $source); $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : ''); if (preg_match("/$evilexpr/i", $stripped)) { return '/* evil! */'; } $strict_url_regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims'; // cut out all contents between { and } while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { $nested = strpos($source, '{', $pos+1); if ($nested && $nested < $pos2) // when dealing with nested blocks (e.g. @media), take the inner one $pos = $nested; $length = $pos2 - $pos - 1; $styles = substr($source, $pos+1, $length); // check every line of a style block... if ($allow_remote) { $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); foreach ($a_styles as $line) { $stripped = preg_replace('/[^a-z\(:;]/i', '', $line); // ... and only allow strict url() values if (stripos($stripped, 'url(') && !preg_match($strict_url_regexp, $line)) { $a_styles = array('/* evil! */'); break; } } $styles = join(";\n", $a_styles); } $key = $replacements->add($styles); $repl = $replacements->get_replacement($key); $source = substr_replace($source, $repl, $pos+1, $length); $last_pos = $pos2 - ($length - strlen($repl)); } // remove html comments and add #container to each tag selector. // also replace body definition because we also stripped off the <body> tag $source = preg_replace( array( '/(^\s*<\!--)|(-->\s*$)/m', '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im', '/'.preg_quote($container_id, '/').'\s+body/i', ), array( '', "\\1#$container_id \\2", $container_id, ), $source); // put block contents back in $source = $replacements->resolve($source); return $source; }