'. ''. ''. $ed_data. ''; $dom = lib_xml_parse($ed_data); if (is_string($dom)) { return NULL; } remove_markup_not_of_ns($dom->documentElement, XHTML_NS); purge_non_wysiwyg_markup_content($dom->documentElement); return $in_elm->ownerDocument->importNode($dom->documentElement, TRUE); } function purge_non_wysiwyg_markup_content($node) { for ($child = $node->firstChild; $child !== NULL; $child = $next_sibling) { $next_sibling = $child->nextSibling; if ($child->nodeType === XML_ELEMENT_NODE) { purge_non_wysiwyg_markup_content($child); if (!is_safe_wysiwyg_element($child)) { remove_node_shallow($child); } else { purge_non_wysiwyg_attributes($child); } } } } function purge_non_wysiwyg_attributes($element) { $i = 0; while (($attr = $element->attributes->item($i)) !== NULL) { if (!is_safe_wysiwyg_attribute($attr)) { $element->removeAttributeNode($attr); $i = 0; } else { ++$i; } } } function is_safe_wysiwyg_element($element) { static $wysiwyg_elements = array( 'a', 'abbr', 'acronym', 'address', 'area', 'b', 'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'map', 'menu', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var' ); return $element->namespaceURI === XHTML_NS && in_array($element->localName, $wysiwyg_elements, TRUE); } function is_safe_wysiwyg_attribute($attr) { static $wysiwyg_attributes = array( 'abbr', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'char', 'charoff', 'class', 'clear', 'color', 'colspan', 'compact', 'coords', 'datetime', 'dir', 'face', 'frame', 'headers', 'height', 'hreflang', 'hspace', 'id', 'ismap', 'lang', 'name', 'nohref', 'noshade', 'nowrap', 'rel', 'rev', 'rowspan', 'rules', 'scope', 'shape', 'size', 'span', 'start', 'summary', 'tabindex', 'title', 'valign', 'value', 'vspace', 'width_str' ); return $attr->namespaceURI === NULL && (in_array($attr->localName, $wysiwyg_attributes, TRUE) || is_safe_wysiwyg_including_attribute($attr) || is_safe_wysiwyg_hyperlinking_attribute($attr) || is_safe_wysiwyg_style_attribute($attr) ); } # Check that including attribute is safe for WYSIWYG edition: i.e. its URI includes # a site local resource (no hotlinking, no javascript scheme, no scheme at all in fact). function is_safe_wysiwyg_including_attribute($attr) { static $including_attributes = array('src', 'usemap'); if (!in_array($attr->localName, $including_attributes, TRUE)) { return FALSE; } $url = parse_w2ml_uri($attr->value); return !isset($url['scheme']); } # Check that hyperlinking attribute is safe for WYSIWYG edition: i.e. its URI uses # a known scheme not useable for XSS. function is_safe_wysiwyg_hyperlinking_attribute($attr) { static $hyperlink_attributes = array('cite', 'href', 'longdesc'); static $safe_schemes = array('http', 'https', 'ftp', 'gopher', 'mailto', 'news'); if (!in_array($attr->localName, $hyperlink_attributes, TRUE)) { return FALSE; } $url = @parse_url($attr->value); return !isset($url['scheme']) || in_array($url['scheme'], $safe_schemes, TRUE); } function is_safe_wysiwyg_style_attribute($attr) { if ($attr->localName !== 'style') { return FALSE; } # From CSS 2.1 grammar (simplified and ASCII-only) # Accept declarations like: # color:#000; font: x-large/110% "new century schoolbook", serif # but reject declarations with URI and property values not looking like a measure, # color, string or identifier. $nmstart = '[_a-z]'; $nmchar = '[_a-z0-9-]'; $ident = '(-?'.$nmstart.$nmchar.'*)'; $string = '("[-+a-z0-9 .,;|\']*"|\'[-+a-z0-9 .,;|"]*\')'; $property = '('.$ident.'\\s*)'; $number = '([0-9]+|[0-9]*\\.[0-9]+)'; $percent = '([+-]?'.$number.'%)'; $length = '([+-]?'.$number.'(px|cm|mm|in|pt|pc))'; $angle = '([+-]?'.$number.'(deg|rad|gad))'; $ems = '([+-]?'.$number.'em)'; $exs = '([+-]?'.$number.'ex)'; $time = '([+-]?'.$number.'(ms|s))'; $freq = '([+-]?'.$number.'(hz|khz))'; $hexcolor = '(\\#[a-f0-9]{3}([a-f0-9]{3})?)'; $rgbcolor = '(rgb\(\\s*[0-9]+\\s*,\\s*[0-9]+\\s*,\\s*[0-9]+\\s*\))'; $term = '(('.$number .'|'.$percent .'|'.$length .'|'.$angle .'|'.$ems .'|'. $exs .'|'.$time .'|'.$freq .'|'.$ident .'|'.$string.'|'. $hexcolor.'|'.$rgbcolor.')\\s*)'; $operator = '[/,]?\\s*'; $expr = '('.$term.'('.$operator.$term.')*)'; $prio = '(!\\s*important\\s*)'; $declaration = '(('.$property.':\\s*'.$expr.$prio.'?)|\\s*)'; $declarations = '\\s*'.$declaration.'(;\\s*'.$declaration.')*'; return preg_match('@^'.$declarations.'$@i', $attr->value) === 1; } ?>