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 - JavaScript

正規表現置換でごり押し。
可読性最悪ですみません。
 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
var input = "<a href='www.google.com' name='hoge' title=\"fuga\">\
link</a><blink>and</blink> <strong onClick='alert(\"NG\")'>click<br/>me!</strong>";


var reg = /<\s*(a|strong|br)((?:\s+\w+\s*=\s*(["']).*?\3)*)?\s*(?:>(([^<]*|<[^<>]*[^\/]>|<[^<>]*\/>)*)<\/s*\1\s*>|\/>)/gmi;

function deleteAttr(attr) {
  return attr.replace(/\s+(\w+)\s*=\s*(["'])(.*?)\2/g, function(all, name, q, value) {
    if(!name.match(/name|href/i)) return '';
    else return all;
  });
}
function eacapeTags(str) {
  var escaped = [];
  str = str.replace(reg, function(all, tag, attr, q, inner) {
    attr = tag.toUpperCase() == 'A' ? deleteAttr(attr) : '';
    escaped .push(tag+ " " + attr, inner);
    return "<A/>";
  }).replace(/</g, "&lt;");
  str = str.replace(/&lt;A\/>/g, function() {
    var tag = escaped.shift(), inner = escaped.shift();
    if(inner) return "<" + tag + ">" + eacapeTags(inner) + "</" + tag + ">";
    else return "<" + tag + "/>";
  });
  return str;
}
document.body.innerHTML=(eacapeTags(input));

inとoutにWSHを使ってます。

 cscript "javascriptファイル"  フィルタしたい文字列

で実行可能です。
 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
if (WScript.Arguments.length != 1) 
	WScript.Quit();
var target = WScript.Arguments.item(0);

var ret = target.replace(
	/<((\/?)([a-z]+)(.*?)(\/?))>/ig,
    function(all, ins, head, tag, elms, tail){
    	switch (tag.toUpperCase())
    	{
			case "A":
				var filterElms = elms.match(/ ?(href|name) *= *[^ \/>]+/ig);
				var newElement = "";
				if (filterElms)
					for(var i = 0; i < filterElms.length; i++)
						newElement += filterElms[i];
				
				return "<" + head + tag + newElement + tail +">";

			case "BR":
			case "STRONG":
				return "<" + head + tag + tail + ">";

			default:
				return "&lt;" + ins + ">";
				break;
    	}
    });

WScript.Echo(ret);

ちょっと無駄に難しく作りすぎました。
開きタグと閉じタグは個別に扱っても構わなかったのか。
ところで、(要件に含まれるのかわかりませんが)属性に">"が含まれるケースを考慮してない回答が結構ありますね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var input = "<a title=\"(>_<;)\" href='www.google.com' name='hoge'>\
link</a><blink>and</blink> <strong onClick='alert(\"NG\")'>click<br/>me!</strong>";

function deleteAttr(attr) {
  return attr.replace(/\s+(\w+)\s*=\s*(["'])(.*?)\2/g, function(all, name, q, value) {
    return name.match(/name|href/i) ? all : '';
  });
}
function filter(html) {
  return html.replace(/<(\/?)(\w+)((?:\s+\w+\s*=\s*(["']).*?\4)*)?(\/?)>/gmi,
      function(all, fslash, tag, attrs, q, rslash) {
         switch(tag.toUpperCase()) {
           case 'STRONG' :  // drop through
           case 'BR' : attrs = ''; break;
           case 'A' : attrs = deleteAttr(attrs); break;
           default : return all.replace('&', '&amp;').replace('<', '&lt;');
         }
         return '<' + fslash + tag + attrs + rslash + '>';
      });
}

document.body.innerHTML=filter(input);

>http://ja.doukaku.org/comment/2722/
失念してました。

文中に">"等が入っても通るように正規表現修正。
 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
if (WScript.Arguments.length != 1) 
	WScript.Quit();
var target = WScript.Arguments.item(0);

var ret = target.replace(
	/<(\/?)(\w+)((\s*|\w+|\w+\s*=\s*('[^']*'|"[^"]*"|\w+))*)(\/?)>/ig,
    function(all, head, tag, attr, nouse1, nouse2, tail){
    	switch (tag.toUpperCase())
    	{
			case "A":
				var attrs = attr.match(/\s?(href|name)\s*=\s*('[^']*'|"[^"]*"|\w+)/ig);
				var newAttr = "";
				if (attrs)
					for(var i = 0; i < attrs.length; i++)
						newAttr += attrs[i];
				return "<" + head + tag + newAttr + tail +">";

			case "BR":
			case "STRONG":
				return "<" + head + tag + tail + ">";

			default:
				return "&lt;" + head + tag + attr + tail + ">";
    	}
    });

WScript.Echo(ret);

 DOM を使った方がより JavaScript らしいのかもしれないが…。

javascript:document.body.innerHTML=(function(s,r,x){r=/\b(?:name|href) *= *(?:".*?"|'.*?'|[^ >]*)/gi;x=/^\/?(a)|br|strong\b/i;return s.replace(/<([^ >]+ ?)((?:".*?"|'.*?'|[^\/>])*)/g,function(m,t,a){return x.test(t)?'<'+t+(RegExp.$1&&(m=a.match(r))?m.join(' '):''):'&lt;'+t+a})})(document.body.innerHTML)
1
2
3
4
5
6
7
8
function doukaku54(s){
  var rx_nh = /\b(?:name|href) *= *(?:".*?"|'.*?'|[^ >]*)/gi;
  var rx_ok = /^\/?(a)|br|strong\b/i, LT = '<', lt = '&lt;', sp = ' ';
  return s.replace(/<([^ >]+ ?)((?:".*?"|'.*?'|[^\/>])*)/g, function(m, tag, ats){
    return rx_ok.test(tag)
      ? LT + tag + (RegExp.$1 && (m = ats.match(rx_nh)) ? m.join(sp) : '')
      : lt + tag + ats });
}

 色々と間違っていたのを修整。(要素中の<>にエスケープが要らないとは知らなんだ。)
 この程度の処理だと大して効果が無いようなので,文字列のキャッシュはやめにした。

javascript:with(document.body)(function(s,r,g,x){r=/\b(?:name|href) *= *(?:".*?"|'.*?'|[^ >]*)/gi;g=/</g;x=/^<\/?(?:(a)|br|strong)\b/i;innerHTML=s.replace(/(<[^ >]+ ?)((?:".*?"|'.*?'|[^>])*?)(?=\/?>)/g,function(m,t,a){return x.test(t)?t+(RegExp.$1&&(m=a.match(r))?m.join(' '):''):m.replace(g,'&lt;')})})(innerHTML)
1
2
3
4
5
6
7
8
function doukaku54(s){
  var rx_nh = /\b(?:name|href) *= *(?:".*?"|'.*?'|[^ >]*)/gi,
      rx_ok = /^<\/?(?:(a)|br|strong)\b/i, rx_lt = /</g;
  return s.replace(/(<[^ >]+ ?)((?:".*?"|'.*?'|[^>])*?)(?=\/?>)/g, function(m, tag, ats){
    return rx_ok.test(tag)
      ? tag + (RegExp.$1 && (m = ats.match(rx_nh)) ? m.join(' ') : '')
      : m.replace(rx_lt, '&lt;') });
}

Index

Feed

Other

Link

Pathtraq

loading...