<?php
class XSSFilter {
static $allowedElements = array(
'a',
'h3', 'h2',
'img',
'ul', 'ol', 'li',
'blockquote',
'abbr', 'acronym',
'sub', 'sup',
'small',
'q',
'del', 'ins',
'code'
);
static $allowedAttributes = array(
'a' => array('href', 'title'),
'img' => array('src', 'alt', 'width', 'height', 'style'),
);
private $parser;
private $started;
protected $out;
function __construct() {
$this->parser = xml_parser_create('utf-8');
xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false);
xml_set_object($this->parser, $this);
xml_set_element_handler($this->parser, 'startElement', 'endElement');
xml_set_character_data_handler($this->parser, 'characters');
}
function __destruct() {
xml_parser_free($this->parser);
}
public function parse($text) {
if (xml_parse($this->parser, '<root>'.$text.'</root>') !== 1) {
$code = xml_get_error_code($this->parser);
throw new ParsingError(xml_error_string($code), $code, $this->parser);
}
return $this->out;
}
protected function startElement($parser, $name, $attrs) {
if ($name == 'root') {
$this->started = true;
$this->out = '';
return;
}
if (!in_array($name, self::$allowedElements)) {
throw new ParsingError("{$name} есть недопустимый элемент.", 0, $parser);
}
if (array_key_exists($name, self::$allowedAttributes)) {
$allowedAttrs = self::$allowedAttributes[$name];
} else {
$allowedAttrs = null;
}
$out = '<' . $name;
foreach ($attrs as $attr => $value) {
if (is_null($allowedAttrs) OR !in_array($attr, $allowedAttrs)) {
throw new ParsingError("{$attr} есть недопустимый аттрибут для элемента {$name}.", 0, $parser);
}
$out .= ' ' . $attr . '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
}
$out .= '>';
$this->out .= $out;
}
protected function endElement($parser, $name) {
if ($name == 'root') {
return;
}
$this->out .= "</{$name}>";
}
protected function characters($parser, $data) {
$this->out .= htmlspecialchars($data, ENT_NOQUOTES, 'UTF-8');
}
}
class ParsingError extends Exception {
public $row;
public $col;
public function __construct($message, $code, $parser) {
parent::__construct($message, $code);
$this->col = xml_get_current_column_number($parser);
$this->row = xml_get_current_line_number($parser);
}
}
// test code below
?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title />
</head>
<body>
<form action="test.php" method="post">
<p>
<textarea name="test" cols="50" rows="25"><?php
echo htmlspecialchars(@$_POST['test'], ENT_COMPAT, 'UTF-8');
?></textarea>
</p>
<p>
<button type="submit">Submit</button>
</p>
</form>
<?php
if (@$_POST['test']) {
$o = new XSSFilter();
try {
$text = $o->parse($_POST['test']);
echo '<p>', $text, '</p>';
} catch (ParsingError $e) {
echo '<h2>', htmlspecialchars($e->getMessage()), '</h2>';
echo "<p>Строка {$e->row}, столбец {$e->col}.</p>";
}
}
?>
</body>
</html>