<?php
define('EXCEPTION_LOG', false);
define('HTML_TAG_NAME_LENGTH_LIMIT', 12);
define('ALLOW_SLASHES_IN_ATTR', true);
class html_clear {
	var $needle_replacement = array(
		"/<script.*>.*<\/script.*>/isU" => '',
		"/(<[\w]{1,11}\s+[^>]*)on\w{4,16}\s*=\s*[^>]*([^>]*[\/]?>)/isU" => '${1}${2}',
		"/<a[^>]*href\s*=\s*[\"\']?\s*javascript\s*:[^>]*[\"\']?.*>(.*)<\/a.*>/isU" => '${1}',
		'/<!--.*-->/sU' => '',
		'/<!--.*/su' => '',
	);
	var $allow_tag = array('br', 'hr', 'span', 'font', 'address', 'pre', 'div', 'p', 'img', 'a', 'table', 'caption',  'tbody', 'thead', 'tfoot', 'tr', 'td', 'th', 'b', 'strong', 'u', 'i', 'h3', 'h1','h2','h4', 'em', 'ul', 'ol', 'li', 'dt', 'dd', 'dl', 'blockquote','q','code', 'center', 'strike', 'sub', 'sup', 'label','object', 'param', 'embed', 'applet', 'iframe', 'script');
	var $abs_forbidden_tag = array('style', 'area', 'base', 'basefont', 'frameset', 'frame', 'noscript');
	var $allow_un_close = array('img', 'br', 'hr');
	var $forbidden_attr = array();

	function html_clear($focus = false) {
		if($focus == true){
			$tags = array('object', 'param', 'embed', 'applet', 'iframe', 'script');
			foreach($tags as $val){
				$this->abs_forbidden_tag[] = $val;
			}
		}
	} 

	function set_forbidden_attr($tags){
		if(is_array($tags)){
			foreach($tags as $val){
				$this->forbidden_attr[] = $val;
			}	
		}else{
			$this->forbidden_attr[] = $tags;
		}
	}

	function clear(&$str) {
		if (strpos($str, '<!--') !== false) $str = preg_replace(array_keys($this -> needle_replacement), array_values($this -> needle_replacement), $str);
		$str = $this -> clear_forbidden($str);
		return $str;
	} 

	function clear_forbidden(&$str) {
		$res = '';
		$cursor = $i = 0;
		if (strpos($str, '<', 0) === false) return $str;
		$len = strlen($str);
		while ($i < $len) {
			$lt_pos = $i = strpos($str, '<', $i);
			if ($i === false) return $res . substr($str, $cursor);
			$i++;
			$tag_name = $this -> get_tagname($str, $i);
			$tag_name_lower = strtolower($tag_name);
			$tag_name_len = strlen($tag_name);
			if (!$tag_name) {
				$i++;
				continue;
			} 
			$tmp_gt_pos = strpos($str, '>', $lt_pos);
			$i = $tmp_gt_pos === false ? $len : $tmp_gt_pos + 1;
			$close_pos = $this -> tag_closed_pos($str, $tag_name, $lt_pos);
			if (in_array($tag_name_lower, $this -> abs_forbidden_tag)) {
				$res .= substr($str, $cursor, $lt_pos - $cursor);
				$cursor = $i = $close_pos === false ? $len : $close_pos;
				continue;
			} 
			if (!in_array($tag_name_lower, $this -> allow_tag) or ($close_pos === false and !in_array($tag_name_lower, $this -> allow_un_close))) {
				$res .= substr($str, $cursor, $lt_pos - $cursor);
				$cursor = $i;
				continue;
			} 
			$this -> clear_forbidden_attr($str, $lt_pos, $i, $tag_name_len, $res, $cursor);
		} 
		if ($cursor === 0) return $str;
		return $res . substr($str, $cursor);
	} 

	function clear_forbidden_attr(&$str, $lt_pos, $gt_pos, $tag_name_len, &$res, &$cursor) {
		$attr_str = substr($str, $lt_pos + $tag_name_len + 1, $gt_pos - $tag_name_len - $lt_pos - 1);
		$attr_info = $this -> get_attr_info($attr_str);
		$forbidden_attr = array();
		foreach ($attr_info as $val) $forbidden_attr[] = (in_array(strtolower($val[0]), $this -> forbidden_attr) or array_pop($val)) ? implode('', $val) : '';
		if (!empty($forbidden_attr)) {
			$res .= substr($str, $cursor, $lt_pos - $cursor);
			$res .= str_replace($forbidden_attr, '', substr($str, $lt_pos, $gt_pos - $lt_pos));
			$cursor = $gt_pos;
		} 
	} 

	function get_attr_info($str) {
		$attr_info = array();
		$str = trim(trim($str, '>')) . ' ';
		for ($i = 0, $len = strlen($str), $attr_name = $attr_equal = $attr_value = '', $attr_force_clear = false, $assert = 1, $quote = ''; $i < $len; $i++) {
			$asc = ord($str[$i]);
			switch ($assert) {
				case 1 :
					if ($this -> is_html_sign($asc))
						$attr_name .= $str[$i];
					else {
						if (strlen(trim($str[$i])) === 0) {
							$attr_equal .= $str[$i];
							$assert = 2;
						} else {
							if ($asc === 61) {
								$attr_equal .= $str[$i];
								$assert = 3;
							} else {
								if ($asc === 47) $attr_equal .= ALLOW_SLASHES_IN_ATTR ? '' : $str[$i];
								$assert = 4;
							} 
						} 
					} 
					break;
				case 2 :
					if (strlen(trim($str[$i])) !== 0) {
						if ($asc === 61) {
							$attr_equal .= $str[$i];
							$assert = 3;
						} else {
							$i--;
							$assert = 4;
						} 
					} else $attr_equal .= $str[$i];
					break;
				case 3 :
					if (strlen(trim($str[$i])) !== 0) {
						if ($asc === 34 or $asc === 39) {
							if ($quote) {
								if ($quote === $str[$i]) {
									$quote = '';
									$assert = 4;
								} 
							} else $quote = $str[$i];
						} 
						$attr_value .= $str[$i];
					} else {
						if ($i == $len - 1 and $quote) {
							$attr_force_clear = true;
							$i--;
							$assert = 4;
						} else if ($attr_value) {
							if (!$quote) {
								$i--;
								$assert = 4;
							} else $attr_value .= $str[$i];
						} else $attr_equal .= $str[$i];
					} 
					break;
				case 4 :
					if (!(strlen(trim($attr_name)) === 0 and strlen(trim($attr_equal)) === 0 and strlen(trim($attr_value)) === 0))
						$attr_info[] = array($attr_name, $attr_equal, rtrim($attr_value), $attr_force_clear);
					$attr_name = $attr_equal = $attr_value = '';
					$assert = 1;
					if (!$quote) $i--;
					break;
				default :
					if (EXCEPTION_LOG)
						$this -> exception_log_raise($str, $i, $attr_info, $assert);
					$assert = 1;
					break;
			} 
		} 
		return $attr_info;
	} 

	function get_tagname(&$str, $offset) {
		$tag = '';
		for ($i = $str[$offset + 1] === '/' ? $offset + 1 : $offset, $end = HTML_TAG_NAME_LENGTH_LIMIT + $offset; $i < $end; $i++) {
			$asc = ord($str[$i]);
			if ($asc === 47) continue;
			if ($this -> is_html_sign($asc)) $tag .= $str[$i];
			else if ($asc === 60) {
				$tag = '';
				break;
			} else break;
		} 
		return $tag;
	} 

	function tag_closed_pos($str, $tag_name, $lt_pos) {
		$desh_pos = strpos($str, '/', $lt_pos);
		$gt_pos = strpos($str, '>', $lt_pos);
		$close_self_pos = strpos($str, '/>', $lt_pos);
		if ($desh_pos === false or $gt_pos === false) return false;
		if ($desh_pos === $close_self_pos or $gt_pos - 1 === $close_self_pos) return $gt_pos + 1;
		$close_tag_pos = stripos($str, '</' . $tag_name, $lt_pos);
		if ($close_tag_pos === false) return false;
		$close_pos = strpos($str, '>', $close_tag_pos);
		return ($close_pos === false) ? false : $close_pos + 1;
	} 

	function is_html_sign($asc) {
		return ($asc > 96 and $asc < 123) or ($asc > 64 and $asc < 91) or ($asc > 48 and $asc < 58);
	} 

	function exception_log_raise($attr_str, $pos, $attr_info_even, $attr_assert) {
		$msg = "orginal html attr string : [$attr_str]\nexception at : [$pos], attrbute assertion : $attr_assert\n" . var_export($attr_info_even, true) . "\n\n";
		echo $msg;
	} 
} 
?>