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

SGMLのタグは非常に多様な形式を持つようです。もちろん全部の形式に対応すれば良いのですが、必ずしもブラウザが対応しているとは限らないため、正しい形式のタグであっても万一ブラウザが対応していない事によってセキュリティホールになってしまっては意味がありません。そこでごく一般的な形式のタグしか想定しない事にします。

方針
間違っても、未処理のタグが残らないようにする(想定外であっても)。
想定外の構造のタグが誤って変換されてしまう事はやむを得ないとする。

この条件を満たすため、以下の方法で処理する

変換対象のタグを前処理する
全ての <, > を変換する
前処理したタグを元に戻す

&は題意から変換しないことにしました。尚、HTMLではURL中であっても文字参照は有効(ブラウザが解釈しなければならない)ため特別扱いはしていません。
 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
import java.util.regex.*;

public class Sample {
    private static final Pattern TAG_FILTER = Pattern.compile
        ("<(¥¥w+)((¥¥s+¥¥w+(¥¥s*=¥¥s*(¥"[^¥"]*¥"|'[^']*'|[¥¥w-:]*))?)*)¥¥s*/?¥¥s*>");
    private static final Pattern END_TAG_FILTER = Pattern.compile
        ("(?i)</(A|BR|STRONG)¥¥s*>");
    private static final Pattern ATTR_FILTER = Pattern.compile
        ("(¥¥w+)¥¥s*=¥¥s*(¥"[^¥"]*¥"|'[^']*'|[¥¥w-:]*)");
    public static String sanitizing(String fragment) {
        fragment = fragment.replaceAll("[¥¥p{Cntrl}&&[^¥¥s]]", "");
        Matcher m = TAG_FILTER.matcher(fragment);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            if ("A".equalsIgnoreCase(m.group(1))) {
                String href = null, name = null;
                Matcher m2 = ATTR_FILTER.matcher(m.group(2));
                while (m2.find()) {
                    if ("href".equalsIgnoreCase(m2.group(1))) {
                        href = m2.group(2);
                    } else if ("name".equalsIgnoreCase(m2.group(1))) {
                        name = m2.group(2);
                    }
                }
                String tag = "¥001"+m.group(1) + ((href != null)?" href="+href
                    : "") + ((name != null)? " name="+name : "") + "¥002";
                m.appendReplacement(sb, m.quoteReplacement(tag));
            } else if ("BR".equalsIgnoreCase(m.group(1)) || 
                       "STRONG".equalsIgnoreCase(m.group(1))) {
                m.appendReplacement(sb, "¥001" + m.group(1) + "¥002");
            }
        }
        m.appendTail(sb);
        m = END_TAG_FILTER.matcher(sb.toString());
        fragment = m.replaceAll("¥001/$1¥002");
        fragment = fragment.replaceAll("<", "&lt;");
        fragment = fragment.replaceAll(">", "&gt;");
        fragment = fragment.replaceAll("¥001", "<");
        fragment = fragment.replaceAll("¥002", ">");
        return fragment;
    }
    public static void main(String[] args) throws Exception {
        System.out.println(sanitizing("<script><abc><def ghi=jkl>"));
        System.out.println(sanitizing("<script foo=¥"<script>alert(¥'bar¥')</script>¥">alert(¥'foo¥')</script>"));
        System.out.println(sanitizing("<script foo=¥"<a href=¥'link¥'>link</a>¥" center>alert(¥'foo¥')</script><BR/>"));
        System.out.println(sanitizing("<a href='www.g>oogle.com' id=125>link</a>"));
    }
}

Index

Feed

Other

Link

Pathtraq

loading...