challenge 一部のHTMLタグを通すフィルタ

ユーザが入力した文字列から、一部のタグだけを許可して他をエスケープするコードを書いてください。要件は次のようになります。
  • 通すタグはAとBRとSTRONGのみ。大文字小文字は区別しない。
  • それ以外のタグとして意味を持ちうる文字列は<を&lt;に変換することで無効化する(削除するのではない。>は変換してもしなくてもよい)
  • Aタグのhrefとname以外の属性は削除する。BRやSTRONGの属性はすべて削除する。

このお題はperezvonさんの提案を元にしています。ありがとうございました。 ただ、いきなりだと難しいかと思ったので、肝の部分以外を先に出題しました。このお題は続編で徐々に難しくなっていきます。

追記:属性に<や>が含まれてしまうケースに漏れのある解答が多いようなのでテストケースを追加します。
これは「この出力なら十分」という意味です。この出力の通りでなければいけないという意味ではありません。

<script foo="<script>alert('bar')</script>">alert('foo')</script>
&lt;script foo="&lt;script&gt;alert('bar')&lt;/script&gt;"&gt;alert('foo')&lt;/script&gt;


<script foo="<a href='link'>link</a>">alert('foo')</script>
&lt;script foo="&lt;a href='link'&gt;link&lt;/a&gt;"&gt;alert('foo')&lt;/script&gt;

<a href='www.g>oogle.com'>link</a>

<a href="./www.g%3Eoogle.com">link</a>

Posted feedbacks - PHP

br以外の通したタグの閉じ忘れにも対応してみた。
(閉じる順番が間違ってる場合は今回は大目に見るとして。)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
function safehtml($str)
{
	$r=array();
	$tags=array();
	$offs=0;
	while(preg_match('!<\s*(/|)\s*(([^>\'"]+|\'[^\']*\'|"[^"]*")*)>!',$str,$m1,PREG_OFFSET_CAPTURE,$offs))
	{	$r[]=substr($str,$offs,$m1[0][1]-$offs);
		$offs=$m1[0][1]+strlen($m1[0][0]);
		preg_match_all('!([a-z0-9_]+)(\s*=\s*("[^"]*"|\'[^\']*\'|[^\s]+)|)!im',$m1[2][0],$m2,PREG_SET_ORDER);
		switch(strtolower($m2[0][1]))
		{
		case 'a':
		case 'strong':
			if($m1[1][0])
			{	if(($i=array_search($m2[0][1],$tags))!==false)
					unset($tags[$i]);
			}
			else
				array_unshift($tags,$m2[0][1]);
			$t=array($m1[1][0].$m2[0][1]);
			if(strtolower($m2[0][1])=='a' && !$m1[1][0])
			{	array_shift($m2);
				while($param=array_shift($m2))
				{	switch(strtolower($param[1]))
					{
					case 'href':
					case 'name':
						$t[]=$param[0];
						break;
					}
				}
			}
			$r[]='<'.implode(" ",$t).'>';
			break;
		case 'br':
			$r[]='<br/>';
			break;
		default:
			$r[]='&lt;'.$m1[1][0].$m1[2][0].'&gt;';
			break;
		}
	}
	$r[]=substr($str,$offs);
	while($tag=array_shift($tags)) // 閉じわすれタグを閉じる
		$r[]="</$tag>";
	return implode("",$r);
}

echo safehtml(<<<EOT
<a href='www.google.com' target=_blank>link</a> <blink>and</blink> <strong onClick='alert("NG")'>click<br/>me!</strong>
EOT
);
?>

#2727の指摘に対応。
タグの大文字小文字の問題修正と通すタグに関する情報をまとめて汎用化してみた。
タグは開いたのと逆順に閉じることを強制する。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
function safehtml($str)
{
	$safetag=array('a'=>array(1,array('href','name')),'strong'=>array(1),'br'=>array(0));
	$r=array();
	$tags=array();
	$offs=0;
	while(preg_match('!<(\s*(/|)\s*(([^>\'"/]+|\'[^\']*\'|"[^"]*")*)(/|)\s*)>!',$str,$m1,PREG_OFFSET_CAPTURE,$offs))
	{	$r[]=substr($str,$offs,$m1[0][1]-$offs);
		$offs=$m1[0][1]+strlen($m1[0][0]);
		preg_match_all('!([^\s\'"=]+)(\s*=\s*("[^"]*"|\'[^\']*\'|[^\s]+)|)!im',$m1[3][0],$m2,PREG_SET_ORDER);
		$tag=strtolower($m2[0][1]);
		if(isset($safetag[$tag]))
		{	if($safetag[$tag][0]&1)
			{	if($m1[2][0])
				{	if(array_search($tag,$tags)===false)
						continue; // 開いてないタグは閉じない
					while(($t=array_shift($tags))) // 開いたのと逆順に閉じる
					{	$r[]="</$t>";
						if($t==$tag)
							break;
					}
					continue;
				}
				if(!$m1[5][0])
					array_unshift($tags,$tag);
			}
			$t=array($tag);
			if(isset($safetag[$tag][1]) && !$m1[2][0])
			{	array_shift($m2);
				while($param=array_shift($m2))
				{	if(array_search(strtolower($param[1]),$safetag[$tag][1])!==false)
						$t[]=$param[0];
				}
			}
			$r[]='<'.$m1[2][0].implode(" ",$t).$m1[5][0].'>';
		}
		else
			$r[]=str_replace(array('<','>'),array('&lt;','&gt;'),$m1[0][0]);
	}
	$r[]=substr($str,$offs);
	while(($tag=array_shift($tags))) // 閉じわすれタグを閉じる
		$r[]="</$tag>";
	return implode("",$r);
}

echo safehtml(<<<EOT
<a href='www.google.com' target=_blank>link</a> <blink dummy='<'>and</blink> <strong onClick='alert("NG")'>click<br/>me!</strong> <z foo='<script>alert("Boo")</script>'>
EOT
);
?>

Index

Feed

Other

Link

Pathtraq

loading...