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

TripletaiL フレームワークには一部のタグだけを許可する機能があります.
ただ,題意のサンプルのように,属性値を '' で括った場合は
うまく対応できないので,そこを変更しています.

また,閉じタグの自動修復機能が少しバグっていたので
以下のパッチを当てて実行しています.

--- lib/Tripletail/TagCheck.pm  27 Jun 2007 03:01:50 -0000      1.15
+++ lib/Tripletail/TagCheck.pm  31 Aug 2007 01:03:31 -0000
@@ -253,7 +253,9 @@
                                }

                                # スタックにプッシュ
-                               push @$open_stack, $elem;
+                               if(!$taginfo->mustBeEmpty) {
+                                       push @$open_stack, $elem;
+                               }
                        }
                }
        }

----
実行結果
<<<<

<a href="www.google.com">link</a> <blink>and</blink>
<strong onClick='alert("NG")'>click<br>me!</strong>
>>>>

<a href="www.google.com">link</a> &lt;blink&gt;and&lt;/blink&gt;
<strong>click<br>me!</strong>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/perl

use Tripletail qw(/dev/null);

my $input = q{
<a href="www.google.com">link</a> <blink>and</blink>
<strong onClick='alert("NG")'>click<br>me!</strong>
};

my $tc = $TL->newTagCheck;
$tc->setATarget(undef);
$tc->setAllowTag(':BR;A(HREF,NAME);STRONG()');
my $output = $tc->check($input);

print "<<<<\n";
print $input;
print ">>>>\n";
print $output;

HTML::Parserで割ときっちりと。
Dan the Perl Monger
 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
#!/usr/local/bin/perl
use strict;
use warnings;
use HTML::Parser;
use HTML::Entities;

sub parse {
    my $str = shift;
    my @parsed;
    my $p = HTML::Parser->new(
        start_h => [
            sub {
                my ( $t, $a, $text ) = @_;
                if ($t eq 'a'){
                    my @a =
                        map { qq/$_="/ . encode_entities($a->{$_}) . qq/"/ }
                            grep /^name|href$/, keys %$a;
                    push @parsed, "<a ".join(" ", @a). ">"
                }elsif($t eq 'br' || $t eq 'strong'){
                    push @parsed, "<$t>"
                }else{
                    push @parsed, encode_entities($text)
                }
            }, "tagname, attr, text"
        ],
        end_h => [
            sub {
                my $t = shift;
                push @parsed, $t =~ /^a|br|strong$/ ? "</$t>" : "&lt'/$t&gt;";
            }, "tagname"
        ],
        text_h => [
            sub {
                my $t = shift;
                $t =~ s{\n}{</br>}g;
                push @parsed, $t;
            }, "text"
        ]
    )->parse($str);
    join '', @parsed;
}

# for test
local $/;
my $str = <>;
print parse($str);

短かさにこだわってみました. 
&#x3C; とかは反則ですか?
1
2
3
4
5
6
7
8
#! /usr/bin/perl
sub E($){ local $_ = shift; s/([&<>"'])/sprintf('&#x%02X;',ord $1)/eg; $_; }
sub T($){ local $_ = shift; no warnings;
          s#^<(/?(?:br|strong))\b.*$#<$1>#i ? $_ :
            s#^<(/?a\b)[^>]*?(\s?(?:href|name)=(?:'[^']*'|"[^"]*"|[^>\s]*))?
              [^>]*?(\s?(?:href|name)=(?:'[^']*'|"[^"]*"|[^>\s]*))?[^>]*?>
              #<$1$2$3>#xi ? $_ : E $_;  }
$_ = join '', <>; s#(.*?)(</?\w+\b[^>]*>)#E($1).T($2)#eg; print;

Index

Feed

Other

Link

Pathtraq

loading...