"&"の文字を実体参照に変換する場合に、数値文字参照の形で書かれた場合は避けるようにしてみる。
PerlでCGIを作成する場合、HTMLで使用される特殊な文字を(安全のため)実体参照に変換する以下のような処理がよく使われると思います。
sub sanitize {
my $arg = shift;
$arg =~ s/&/&/g;
$arg =~ s/</</g;
$arg =~ s/>/>/g;
$arg =~ s/"/"/g;
return $arg;
}
しかし、例えばハートマーク♥(u+2665)を数値文字参照(♥ or ♥)を使って入力したい、という要求に対しては、上記の処理では応えられないことになります。もしそれを実現するとしたら、「"&"は"&"に変換するが、数値文字参照の形式で入力された場合はこの&を変換しない」という処理を行う必要があります。
Perlの正規表現には否定先読みの指定( (?!regexp) )があるので、これを使用して
sub sanitize {
my $arg = shift;
$arg =~ s/&(?!#\d+;)/&/g;
...
とすれば、"&"のうち、そのあとに『"#"、数字の1字以上の並び、";"』が続かないものを変換、とすることができます。
しかしながら、UnicodeのうちXML, HTMLで使用できる文字コードには上限があり、またそれ以下のコードの中にも実際には使用できないものあるため、上記のような単純な正規表現では対処不十分な可能性があります。
というわけで、実際に使える文字コードの範囲をきちっと確認した上で、それにマッチする正規表現を作ることを考えてみました。
HTML 4.01の場合。SGML宣言を参照しました。
| 十六進 | 十進 |
|---|---|
| x9 - xA | 9 - 10 |
| xD | 13 |
| x20 - x7E | 32 - 126 |
| xA0 - xD7FF | 160 - 55295 |
| xE000 - xFFFD | 57344 - 65533 |
| x10000 - x10FFFF | 65536 - 1114111 |
XML(XHTML)との互換のため、xFFFE と xFFFF を除いています。詳しくは以下を参照。
まずは十進数形式の文字参照をそのまま入力できることを目標にしてみます。十六進形式の文字参照は対応していないブラウザもありそうなので。
※以下はメモ段階。
範囲: 9, 10, 13, 32 - 99
a. 9 9 b. 11, 13 1[03] c. 32 - 39 3[2-9] d. 40 - 99 [4-9][0-9] a+b+c+d : 9|1[03]|3[2-9]|[4-9][0-9]
範囲: 100 - 126, 160 - 999
a. 100 - 119 1[01]\d b. 120 - 126 12[0-6] c. 160 - 199 1[6-9]\d d. 200 - 999 [2-9]\d\d a+c : 1[016-9]\d a+b+c : 1([016-9]\d|2[0-6]) a+...+d : 1([016-9]\d|2[0-6])|[2-9]\d\d
All OK. \d\d\d\d
範囲: 10000 - 55295, 57344 - 65533, 65536 - 99999
a. 10000 - 49999 [1-4]\d\d\d\d
b. 50000 - 54999 5[0-4]\d\d\d
c. 55000 - 55199 55[01]\d\d
d. 55200 - 55289 552[0-8]\d
e. 55290 - 55295 5529[0-5]
f. 57344 - 57349 5734[4-9]
g. 57350 - 57399 573[5-9]\d
h. 57400 - 57999 57[4-9]\d\d
i. 58000 - 59999 5[89]\d\d\d
j. 60000 - 64999 6[0-4]\d\d\d
k. 65000 - 65499 65[0-4]\d\d
l. 65500 - 65529 655[0-2]\d
m. 65530 - 65533 6553[0-3]
n. 65536 - 65539 6553[6-9]
o. 65540 - 65599 655[4-9]\d
p. 65600 - 65999 65[6-9]\d\d
q. 66000 - 69999 6[6-9]\d\d\d
r. 70000 - 99999 [7-9]\d\d\d\d
a+r :
[1-47-9]\d\d\d\d
b+i : 5[0-489]\d\d\d
c+d+e : 55([01]\d\d
|2([0-8]\d
|9[0-5]))
f+g+h : 57(3(4[4-9]
|[5-9]\d)
|[4-9]\d\d)
5から :
5([0-489]\d\d\d
|5([01]\d\d
|2([0-8]\d
|9[0-5]))
|7(3(4[4-9]
|[5-9]\d)
|[4-9]\d\d))
j+q : 6[0-46-9]\d\d\d
k+p : 65[0-46-9]\d\d
l+o : 655[0-24-9]\d
m+n : 6553[0-36-9]
6から :
6([0-46-9]\d\d\d
|5([0-46-9]\d\d
|5([0-24-9]\d
|3[0-36-9])))
5桁まとめ :
[1-47-9]\d\d\d\d
|5([0-489]\d\d\d
|5([01]\d\d
|2([0-8]\d
|9[0-5]))
|7(3(4[4-9]
|[5-9]\d)
|[4-9]\d\d))
|6([0-46-9]\d\d\d
|5([0-46-9]\d\d
|5([0-24-9]\d
|3[0-36-9])))
All OK. \d\d\d\d\d\d
4桁の場合とまとめると、\d\d\d\d(\d\d)?
範囲: 1000000 - 1114111
a. 1000000 - 1099999 10\d\d\d\d\d
b. 1100000 - 1109999 110\d\d\d\d
c. 1110000 - 1113999 111[0-3]\d\d\d
d. 1114000 - 1114099 11140\d\d
e. 1114100 - 1114109 111410\d
f. 1114110 - 1114111 111411[01]
a+...+f :
1(0\d\d\d\d\d
|1(0\d\d\d\d
|1([0-3]\d\d\d
|4(0\d\d
|1(0\d
|1[01])))))
0*(
9|1[03]|3[2-9]|[4-9][0-9]
|1([016-9]\d|2[0-6])|[2-9]\d\d
|\d\d\d\d(\d\d)?
|[1-47-9]\d\d\d\d
|5([0-489]\d\d\d
|5([01]\d\d
|2([0-8]\d
|9[0-5]))
|7(3(4[4-9]
|[5-9]\d)
|[4-9]\d\d))
|6([0-46-9]\d\d\d
|5([0-46-9]\d\d
|5([0-24-9]\d
|3[0-36-9])))
1(0\d\d\d\d\d
|1(0\d\d\d\d
|1([0-3]\d\d\d
|4(0\d\d
|1(0\d
|1[01])))))
)
調子に乗って十六進数の場合も探ってみるのですよ。以下接頭辞のxは省略。
範囲: 9, A, D, 20 - 7E, A0 - FF
a. 9, A, D [9ADad] b. 20 - 6F [2-6][\dA-Fa-f] c. 70 - 7E 7[\dA-Ea-e] d. A0 - FF [A-Fa-f][\dA-Fa-f] b+d : [2-6A-Fa-f][\dA-Fa-f] a+b+c+d : [9ADad]|[2-6A-Fa-f][\dA-Fa-f]|7[\dA-Ea-e]
All OK.
[\dA-Fa-f][\dA-Fa-f][\dA-Fa-f]
∴[\dA-Fa-f]{3}
範囲: 1000 - D7FF, E000 - FFFD
a. 1000 - CFFF [\dA-Ca-c][\dA-Fa-f][\dA-Fa-f][\dA-Fa-f]
b. D000 - D7FF [Dd][0-7][\dA-Fa-f][\dA-Fa-f]
c. E000 - EFFF [Ee][\dA-Fa-f][\dA-Fa-f][\dA-Fa-f]
d. F000 - FEFF [Ff][\dA-Ea-e][\dA-Fa-f][\dA-Fa-f]
e. FF00 - FFE0 [Ff]{2}[\dA-Ea-e][\dA-Fa-f]
f. FFF0 - FFFD [Ff]{3}[\dA-Da-d]
a+b+c+d : ([\dA-Ca-c][\dA-Fa-f]
|[Dd][0-7]
|[Ee][\dA-Fa-f]
|[Ff][\dA-Ea-e])[\dA-Fa-f][\dA-Fa-f]
e+f : [Ff]{2}([\dA-Ea-e][\dA-Fa-f]
|[Ff][\dA-Da-d])
All OK.
[\dA-Fa-f][\dA-Fa-f][\dA-Fa-f][\dA-Fa-f][\dA-Fa-f]
∴[\dA-Fa-f]{5}
3桁と合わせると[\dA-Fa-f]{3}([\dA-Fa-f]{2})?
範囲: 100000 - 10FFFF
10[\dA-Fa-f][\dA-Fa-f][\dA-Fa-f][\dA-Fa-f]
∴ 10[\dA-Fa-f]{4}
x0*(
[9ADad]|[2-6A-Fa-f][\dA-Fa-f]|7[\dA-Ea-e]
|[\dA-Fa-f]{3}([\dA-Fa-f]{2})?
|([\dA-Ca-c][\dA-Fa-f]
|[Dd][0-7]
|[Ee][\dA-Fa-f]
|[Ff][\dA-Ea-e])[\dA-Fa-f][\dA-Fa-f]
|[Ff]{2}([\dA-Ea-e][\dA-Fa-f]
|[Ff][\dA-Da-d])
|10[\dA-Fa-f]{4}
)
use strict;
use Benchmark;
my $a = <<__EOT__;
<"> NG 
<"> OK 	
<"> NG  
<"> OK
<"> NG  
<"> OK   c d ~   ϧ
<"> OK Ϩ ✏ ✐ ퟿
<"> NG � �
<"> OK  �
<"> NG  
<"> OK 𐀀 𘚟 𘚠 󴈿
<"> OK 󴉀 􂀀
<"> NG 􂀁
<"> NG 
<"> OK 	 
 
<"> NG  &xF;  &x1F;
<"> OK   ~   ÿ
<"> OK Ā ࿿ က ퟿
<"> OK 혀   タ ￸
<"> NG � �
<"> OK  � 𐀀 􈀀
<"> NG �
__EOT__
#print sanitize1($a), "\n";
# ベンチマーク用
timethese(2000000, {
'test0' => 'sanitize0($a);',
'test1' => 'sanitize1($a);',
});
# ふつうの変換
sub sanitize0 {
my $arg = shift;
$arg =~ s/&/&/g;
$arg =~ s/</</g;
$arg =~ s/>/>/g;
$arg =~ s/"/"/g;
return $arg;
}
# 数値文字参照の場合はよける変換
sub sanitize1 {
my $arg = shift;
$arg =~ s/&(?!\#
# 部分マッチの後方参照は必要ないため、
# (a|b)ではなく(?:a|b)を使用
(?:
# 十進
0*(?:
9|1[03]|3[2-9]|[4-9][0-9]
|1(?:[016-9]\d|2[0-6])|[2-9]\d\d
|\d\d\d\d(?:\d\d)?
|[1-47-9]\d\d\d\d
|5(?:[0-489]\d\d\d
|5(?:[01]\d\d
|2(?:[0-8]\d
|9[0-5]))
|7(?:3(?:4[4-9]
|[5-9]\d)
|[4-9]\d\d))
|6(?:[0-46-9]\d\d\d
|5(?:[0-46-9]\d\d
|5(?:[0-24-9]\d
|3[0-36-9])))
|1(?:0\d\d\d\d\d
|1(?:0\d\d\d\d
|1(?:[0-3]\d\d\d
|4(?:0\d\d
|1(?:0\d
|1[01])))))
)
|
# 十六進
x0*(?:
[9ADad]|[2-6A-Fa-f][\dA-Fa-f]|7[\dA-Ea-e]
|[\dA-Fa-f]{3}(?:[\dA-Fa-f]{2})?
|(?:[\dA-Ca-c][\dA-Fa-f]
|[Dd][0-7]
|[Ee][\dA-Fa-f]
|[Ff][\dA-Ea-e])[\dA-Fa-f][\dA-Fa-f]
|[Ff]{2}(?:[\dA-Ea-e][\dA-Fa-f]
|[Ff][\dA-Da-d])
|10[\dA-Fa-f]{4}
)
)
;)/&/gx;
$arg =~ s/</</g;
$arg =~ s/>/>/g;
$arg =~ s/"/"/g;
return $arg;
}
正規表現の長さの割には、速度差は大して無いようです。
$ perl a.pl
Benchmark: timing 2000000 iterations of test0, test1...
test0: 14 wallclock secs (13.32 usr + 0.00 sys = 13.32 CPU) @ 150150.15/s
(n=2000000)
test1: 13 wallclock secs (13.29 usr + 0.00 sys = 13.29 CPU) @ 150489.09/s
(n=2000000)
$ perl a.pl
Benchmark: timing 2000000 iterations of test0, test1...
test0: 13 wallclock secs (13.33 usr + 0.00 sys = 13.33 CPU) @ 150037.51/s
(n=2000000)
test1: 13 wallclock secs (13.27 usr + 0.00 sys = 13.27 CPU) @ 150715.90/s
(n=2000000)